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

发布于 2025-01-02 00:20:28 字数 94 浏览 0 评论 0原文

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

I want to draw a grid of 10 x 10 squares on a HTML5 canvas with number 1-100 displayed on the squares. Clicking a square should call a JavaScript function with the square's number passed as a variable to the function.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

染火枫林 2025-01-09 00:20:28

首先,我鼓励您阅读涉及 HTML5 Canvas 的另一个问题的此答案。你需要明白没有正方形。为了检测“正方形”上的单击,您必须跟踪从每个画布坐标到它逻辑上包含的正方形的映射,处理整个画布上的单个单击事件,计算出哪个正方形(s) 您想要更改,然后根据您想要的更改重新绘制画布。

然后,既然您似乎不反对使用更合适的技术,我鼓励您在任一 HTML 中执行此操作(其中每个“方块”类似于

,这绝对是-使用 CSS 定位、调整大小和颜色)或 SVG(如果您需要能够旋转的正方形,或者想要引入其他形状,则使用 )。

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

First, I encourage you to read this answer to another question involving the 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.

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 and SVG are both 'retained-mode' graphics mode systems, where drawing a shape 'retains' the concept of that shape. 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):

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/

Here's the core code, in case JSFiddle is down:

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;
}
风筝有风,海豚有海 2025-01-09 00:20:28

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

Using HTML elements rather than drawing these things on a canvas or using SVG is another option and quite possibly preferable.

enter image description here

Following up on Phrogz's suggestions, see here for an SVG implementation:

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"]));
故人爱我别走 2025-01-09 00:20:28

正如接受的答案所示,如果这就是您的全部设计,那么在 HTML/CSS 中执行此操作是最简单的,但这里有一个使用的示例对于那些使用 canvas 的用例可能更有意义的人来说,canvas 是一种替代方案(并且与 HTML/CSS 并置)。

问题的第一步归结为找出用户鼠标在画布中的位置,这需要知道画布元素的偏移量。这与查找元素中的鼠标位置相同,因此在这方面canvas确实没有什么独特之处。我正在使用 event.offsetX/Y 来执行此操作。

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

最后,将 事件监听器 添加到 canvas 元素以检测鼠标操作,这将触发鼠标位置和所选图块的重新计算以及画布的重新渲染。我将大部分逻辑附加到 mousemove,因为它更容易可视化,但如果您选择,相同的代码也适用于单击事件。

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

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

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).

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. I'm using event.offsetX/Y to do this.

Drawing a grid on canvas amounts to a nested loop for rows and columns. Use a tileSize variable to control the step amount. 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. 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).

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.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文