简体   繁体   English

在 Web 浏览器中创建可点击的网格

[英]Creating a Clickable Grid in a Web Browser

I want to draw a grid of 10 x 10 squares on a HTML5 canvas with number 1-100 displayed on the squares.我想在 HTML5 画布上绘制一个 10 x 10 方块的网格,方块上显示数字 1-100。 Clicking a square should call a JavaScript function with the square's number passed as a variable to the function.单击一个方块应该会调用一个 JavaScript 函数,并将方块的编号作为变量传递给函数。

First, I encourage you to read this answer to another question involving the HTML5 Canvas.首先,我鼓励您阅读有关 HTML5 Canvas 的另一个问题的答案 You need to understand that there are no squares.你需要明白没有正方形。 In order to detect a click on a 'square', you would have to keep track of a mapping from each canvas coordinate to the square(s) that it logically contains, handle a single click event on the entire canvas, work out which square(s) you want to change, and then redraw the canvas with the changes you want.为了检测对“正方形”的点击,您必须跟踪从每个画布坐标到它逻辑包含的正方形的映射,处理整个画布上的单击事件,找出哪个正方形(s) 你想改变,然后用你想要的改变重画画布。

Then—since you seem to have no objection to using a more appropriate technology—I encourage you to do this in either HTML (where each 'square' is something like a <div> that is absolutely-positioned and sized and colored using CSS), or SVG (using <rect> if you need the squares to be able to be rotated, or want to introduce other shapes).然后——因为你似乎不反对使用更合适的技术——我鼓励你在任何一种 HTML 中这样做(其中每个“方块”类似于一个<div> ,它使用 CSS 绝对定位、大小和颜色) , 或 SVG (如果您需要正方形能够旋转,或者想要引入其他形状,请使用<rect> )。

HTML and SVG are both 'retained-mode' graphics mode systems, where drawing a shape 'retains' the concept of that shape. HTML 和 SVG 都是“保留模式”图形模式系统,其中绘制形状“保留”了该形状的概念。 You can move the shape, change its colors, size, etc. and the computer will automatically redraw it for you.您可以移动形状,更改其颜色、大小等,计算机会自动为您重新绘制。 Moreover, and more importantly for your use case, you can (with both HTML and SVG):此外,对于您的用例更重要的是,您可以(使用 HTML 和 SVG):

function changeColor(evt){
  var clickedOn = evt.target;
  // for HTML
  clickedOn.style.backgroundColor = '#f00';

  // for SVG
  clickedOn.setAttribute('fill','red');
}
mySquare.addEventListener('click',changeColor,false);

Edit : I've created a simple implementation in JavaScript and HTML: http://jsfiddle.net/6qkdP/2/编辑:我在 JavaScript 和 HTML 中创建了一个简单的实现: http : //jsfiddle.net/6qkdP/2/

Here's the core code, in case JSFiddle is down:这是核心代码,以防 JSFiddle 关闭:

function clickableGrid( rows, cols, callback ){
  var i=0;
  var grid = document.createElement('table');
  grid.className = 'grid';
  for (var r=0;r<rows;++r){
    var tr = grid.appendChild(document.createElement('tr'));
    for (var c=0;c<cols;++c){
      var cell = tr.appendChild(document.createElement('td'));
      cell.innerHTML = ++i;
      cell.addEventListener('click',(function(el,r,c,i){
        return function(){ callback(el,r,c,i); }
       })(cell,r,c,i),false);
    }
  }
  return grid;
}

EDIT: Using HTML elements rather than drawing these things on a canvas or using SVG is another option and quite possibly preferable.编辑:使用 HTML 元素而不是在画布上绘制这些东西或使用 SVG 是另一种选择,而且很可能更可取。

在此处输入图片说明

Following up on Phrogz's suggestions, see here for an SVG implementation:跟进 Phrogz 的建议,请参阅此处了解 SVG 实现:

jsfiddle example jsfiddle 示例

document.createSvg = function(tagName) {
    var svgNS = "http://www.w3.org/2000/svg";
    return this.createElementNS(svgNS, tagName);
};

var numberPerSide = 20;
var size = 10;
var pixelsPerSide = 400;



var grid = function(numberPerSide, size, pixelsPerSide, colors) {
    var svg = document.createSvg("svg");
    svg.setAttribute("width", pixelsPerSide);
    svg.setAttribute("height", pixelsPerSide);
    svg.setAttribute("viewBox", [0, 0, numberPerSide * size, numberPerSide * size].join(" "));

    for(var i = 0; i < numberPerSide; i++) {
        for(var j = 0; j < numberPerSide; j++) {
          var color1 = colors[(i+j) % colors.length];
          var color2 = colors[(i+j+1) % colors.length];  
          var g = document.createSvg("g");
          g.setAttribute("transform", ["translate(", i*size, ",", j*size, ")"].join(""));
          var number = numberPerSide * i + j;
          var box = document.createSvg("rect");
          box.setAttribute("width", size);
          box.setAttribute("height", size);
          box.setAttribute("fill", color1);
          box.setAttribute("id", "b" + number); 
          g.appendChild(box);
          var text = document.createSvg("text");
          text.appendChild(document.createTextNode(i * numberPerSide + j));
          text.setAttribute("fill", color2);
          text.setAttribute("font-size", 6);
          text.setAttribute("x", 0);
          text.setAttribute("y", size/2);
          text.setAttribute("id", "t" + number);
          g.appendChild(text);
          svg.appendChild(g);
        }  
    }
    svg.addEventListener(
        "click",
        function(e){
            var id = e.target.id;
            if(id)
                alert(id.substring(1));
        },
        false);
    return svg;
};

var container = document.getElementById("container");
container.appendChild(grid(5, 10, 200, ["red", "white"]));
container.appendChild(grid(3, 10, 200, ["white", "black", "yellow"]));
container.appendChild(grid(7, 10, 200, ["blue", "magenta", "cyan", "cornflowerblue"]));
container.appendChild(grid(2, 8, 200, ["turquoise", "gold"]));

As the accepted answer shows, doing this in HTML/CSS is easiest if this is all your design amounts to, but here's an example using canvas as an alternative for folks whose use case might make more sense in canvas (and to juxtapose against HTML/CSS).正如公认的答案所示,如果这是您的所有设计,那么在 HTML/CSS 中执行此操作是最简单的,但这里有一个示例,使用画布作为替代方案,适用于那些用例在画布中可能更有意义的人(并与 HTML/ CSS)。

The first step of the problem boils down to figuring out where in the canvas the user's mouse is, and that requires knowing the offset of the canvas element.问题的第一步归结为找出用户鼠标在画布中的位置,这需要知道画布元素的偏移量。 This is the same as finding the mouse position in an element , so there's really nothing unique to canvas here in this respect.这与在 element 中查找鼠标位置相同,因此在这方面画布没有任何独特之处。 I'm using event.offsetX/Y to do this.我正在使用event.offsetX/Y来做到这一点。

Drawing a grid on canvas amounts to a nested loop for rows and columns.在画布上绘制网格相当于行和列的嵌套循环。 Use a tileSize variable to control the step amount.使用tileSize变量来控制步长。 Basic math lets you figure out which tile (coordinates and/or cell number) your mouse is in based on the width and height and row and column values.基本数学可让您根据宽度和高度以及行和列值确定鼠标所在的图块(坐标和/或单元格编号)。 Use context.fill... methods to write text and draw squares.使用context.fill...方法编写文本和绘制方块。 I've kept everything 0-indexed for sanity, but you can normalize this as a final step before display (don't mix 1-indexing in your logic, though).我将所有内容都保留为 0 索引以保持理智,但是您可以将其标准化为显示前的最后一步(不过,请不要在逻辑中混合使用 1 索引)。

Finally, add event listeners to the canvas element to detect mouse actions which will trigger re-computations of the mouse position and selected tile and re-renders of the canvas.最后,将事件侦听器添加到画布元素以检测鼠标动作,这将触发鼠标位置和选定图块的重新计算以及画布的重新渲染。 I attached most of the logic to mousemove because it's easier to visualize, but the same code applies to click events if you choose.我将大部分逻辑附加到 mousemove 中,因为它更易于可视化,但如果您选择,相同的代码也适用于单击事件。

Keep in mind that the below approach is not particularly performance-conscious;请记住,以下方法并不是特别注重性能; I only re-render when the cursor moves between cells, but partial re-drawing or moving an overlay to indicate the highlighted element would be faster (if available).我只在光标在单元格之间移动时重新渲染,但部分重新绘制或移动覆盖以指示突出显示的元素会更快(如果可用)。 There are a lot of micro-optimizations I've ignored.我忽略了很多微优化。 Consider this a proof-of-concept.将此视为概念验证。

 const drawGrid = (canvas, ctx, tileSize, highlightNum) => { for (let y = 0; y < canvas.width / tileSize; y++) { for (let x = 0; x < canvas.height / tileSize; x++) { const parity = (x + y) % 2; const tileNum = x + canvas.width / tileSize * y; const xx = x * tileSize; const yy = y * tileSize; if (tileNum === highlightNum) { ctx.fillStyle = "#f0f"; } else { ctx.fillStyle = parity ? "#555" : "#ddd"; } ctx.fillRect(xx, yy, tileSize, tileSize); ctx.fillStyle = parity ? "#fff" : "#000"; ctx.fillText(tileNum, xx, yy); } } }; const size = 10; const canvas = document.createElement("canvas"); canvas.width = canvas.height = 200; const ctx = canvas.getContext("2d"); ctx.font = "11px courier"; ctx.textBaseline = "top"; const tileSize = canvas.width / size; const status = document.createElement("pre"); let lastTile = -1; drawGrid(canvas, ctx, tileSize); document.body.style.display = "flex"; document.body.style.alignItems = "flex-start"; document.body.appendChild(canvas); document.body.appendChild(status); canvas.addEventListener("mousemove", evt => { event.target.style.cursor = "pointer"; const tileX = ~~(evt.offsetX / tileSize); const tileY = ~~(evt.offsetY / tileSize); const tileNum = tileX + canvas.width / tileSize * tileY; if (tileNum !== lastTile) { lastTile = tileNum; ctx.clearRect(0, 0, canvas.width, canvas.height); drawGrid(canvas, ctx, tileSize, tileNum); } status.innerText = ` mouse coords: {${evt.offsetX}, ${evt.offsetX}} tile coords : {${tileX}, ${tileY}} tile number : ${tileNum}`; }); canvas.addEventListener("click", event => { status.innerText += "\\n [clicked]"; }); canvas.addEventListener("mouseout", event => { drawGrid(canvas, ctx, tileSize); status.innerText = ""; lastTile = -1; });

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM