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

问题描述 投票:18回答:3

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

javascript html html5 canvas
3个回答
29
投票

[首先,我鼓励您阅读this answer关于HTML5画布的另一个问题。您需要了解没有正方形。为了检测对“正方形”的单击,您必须跟踪从每个画布坐标到逻辑上包含的正方形的映射,处理整个画布上的单击事件,计算出哪个正方形(s)您想要更改的内容,然后使用您想要的更改重新绘制画布。

然后,因为您似乎不反对使用更合适的技术,所以我建议您使用两种HTML格式进行编码(其中每个“正方形”都像<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);

Edit:我已经用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;
}

11
投票

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

<< img src =“ https://image.soinside.com/eyJ1cmwiOiAiaHR0cHM6Ly9pLnN0YWNrLmltZ3VyLmNvbS9FODdDNy5wbmcifQ==” alt =“在此处输入图像描述”>

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

1
投票

正如accepted answer所示,如果这是您的全部设计工作,则在HTML / CSS中执行此操作最简单,但这是一个示例,使用画布替代使用例可能在画布中更有意义(并列)的人们针对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;
});
© www.soinside.com 2019 - 2024. All rights reserved.