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

20

我想在一个 HTML5 画布上绘制一个 10 行 10 列的方格网格,并在方格上显示数字1-100。单击一个方格应调用一个 JavaScript 函数,将该方格的编号作为变量传递给函数。


1
为什么你需要在画布中完成这个任务?使用HTML轻松实现一百个带有数字的可点击正方形。 - robertc
我对这个还不太熟悉,不知道是否需要在画布上完成。我该如何使用HTML来实现呢?不过我需要能够改变单个方块的背景颜色。 - Fred
3个回答

34

首先,我鼓励您阅读这个答案,它涉及到HTML5 Canvas。需要理解的是没有正方形。为了检测“正方形”的点击事件,您需要跟踪每个画布坐标到逻辑上包含它的正方形的映射,处理整个画布上的单击事件,确定要更改哪些正方形,然后用您想要的更改重新绘制画布。

既然您似乎没有反对使用更合适的技术,我鼓励您使用HTML(其中每个“正方形”类似于使用CSS进行绝对定位、调整大小和着色的<div>),或SVG(如果需要正方形能够旋转或者想引入其他形状,可以使用<rect>)。

HTML和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);

编辑: 我已经在JavaScript和HTML中创建了一个简单的实现:http://jsfiddle.net/6qkdP/2/

以下是核心代码,以防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;
}

1
我已经在我的答案中添加了一个HTML实现。 - Phrogz
非常感谢,我曾尝试使用画布,但这是一个更好的解决方案。完美运作! - Fred
我知道这是一个非常老的帖子,但能否让这个表格填满整个父容器?我已经制作了一个定制版本,我想让它填充Bootstrap网格列col-xs-6,但只有在小型布局上,比如移动设备时,它才填充整个父容器。 - Zeliax
@Zeliax 请将此作为一个独立的问题发布。您的问题是:“我可以让HTML表填充父容器,并具有固定纵横比的单元格尺寸吗?”这可能很难在没有JavaScript的情况下实现。或者,您可以使用SVG创建它,然后您就可以缩放SVG以填充容器而无需担心问题。 - Phrogz
当然,我应该这样做。但是与此同时,我已经想出了如何解决我的问题。对于造成的不便,非常抱歉。 - Zeliax

13

编辑:使用HTML元素而不是在画布上绘制这些图形或使用SVG是另一种选择,很可能更好。

在此输入图片描述

在Phrogz的建议后面,这里有一个SVG实现:

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"]));

2
+1 做得好。两个小建议:1)为NS使用更具描述性的变量名称可能有助于新用户理解和修改代码,2)在使用addEventListener时,请确保始终显式传递第三个参数(在这种情况下为false),否则Firefox会生气。 - Phrogz
浏览器兼容性和教学技巧总是受到赞赏的。 - ellisbben
非常感谢您的建议。Phrogz上面提到的HTML正是我现在所需要的,但随着应用程序的开发,可能需要更多的功能。我对SVG或它与HTML解决方案相比可以做什么一无所知?然而,我会学习更多关于这个问题,看看您的SVG方法是否更适合这个问题。 - Fred

4

所接受的答案所示,如果您的设计只需要这些内容,那么在HTML/CSS中实现最简单,但以下是使用Canvas作为替代方案的示例,为那些在Canvas中更有意义的用户案例提供对比(并与HTML/CSS进行对比)。

问题的第一步归结为确定用户鼠标在Canvas的哪个位置,这需要知道Canvas元素的偏移量。在这方面,与Canvas无关,这与在元素中查找鼠标位置相同。我使用event.offsetX/Y来完成此操作。

在Canvas上绘制网格相当于嵌套循环遍历行和列。使用tileSize变量控制步长。基本数学让您根据宽度和高度以及行和列值计算出鼠标所在的瓷砖(坐标和/或单元格编号)。使用context.fill...方法编写文本和绘制正方形。我将所有内容保持为0索引以保持理智,但是您可以在显示之前将其归一化作为最后一步(但不要在逻辑中混合1索引)。

最后,向Canvas元素添加事件侦听器以检测触发鼠标操作的事件,这将触发鼠标位置和选定瓷砖的重新计算以及Canvas的重新渲染。我将大多数逻辑附加到mousemove上,因为更容易可视化,但是如果您选择,相同的代码也适用于click事件。

请记住,下面的方法并不特别注意性能;我仅在光标在单元格之间移动时重新渲染,但部分重绘或将覆盖层移动以指示突出显示的元素会更快(如果可用)。我忽略了许多微小的优化。将其视为概念验证。

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;
});


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接