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

时间:2012-02-04 10:19:41

标签: javascript html html5 canvas

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

3 个答案:

答案 0 :(得分:28)

首先,我建议您阅读this answer另一个涉及HTML5 Canvas的问题。你需要明白没有正方形。为了检测“正方形”上的点击,您必须跟踪从每个画布坐标到其逻辑上包含的正方形的映射,处理整个画布上的单击事件,找出哪个正方形(s)您想要更改,然后使用所需的更改重绘画布。

然后 - 因为您似乎不反对使用更合适的技术 - 我鼓励您在任一HTML中执行此操作(其中每个'square'类似于<div>,其位置绝对且大小合适并使用CSS)或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 :(得分:10)

编辑:使用HTML元素而不是在画布上绘制这些东西或使用SVG是另一种选择,而且可能更可取。

enter image description here

跟进Phrogz的建议,请参阅此处获取SVG实现:

jsfiddle example

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 :(得分:2)

正如accepted answer所示,如果这是您的全部设计工作,则在HTML / CSS中执行此操作最容易,但这是一个示例,使用canvas作为替代方案,对于在画布中用例更有意义的人们(并与HTML / CSS并置。

问题的第一步归结为找出用户鼠标在画布中的位置,这需要知道画布元素的偏移量。这与finding the mouse position in an element相同,因此在这方面,画布确实没有什么独特之处。我正在使用event.offsetX/Y来做到这一点。

在画布上绘制网格相当于行和列的嵌套循环。使用tileSize变量控制步长。基本数学运算可让您根据宽度和高度以及行和列的值确定鼠标所在的图块(坐标和/或单元格编号)。使用context.fill...方法编写文本并绘制​​正方形。为了理智起见,我将所有索引都设为0,但您可以将其标准化为显示之前的最后一步(不过,请勿在逻辑中混用1索引)。

最后,将event listeners添加到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;
});