如何使用 Canvas 更改 JS 中棋盘的每个框的填充颜色?

How can I change the fill colors of each box for a chessboard in JS, using Canvas?

我刚刚想到用 JS 和 Canvas 画一个棋盘,我有这段代码可以用 for 循环绘制方框。

canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");

var x, y,
  boxWidth = 30,
  boxHeight = 30;

for (x = 0; x < canvas.width; x += boxWidth) {
  for (y = 0; y < canvas.height; y += boxHeight) {
    ctx.beginPath();
    ctx.rect(x, y, boxWidth, boxHeight);
    ctx.stroke();
    ctx.closePath();
  }
}
<canvas id="canvas" width="240" height="240"></canvas>

现在我想知道如何访问轴上的每个奇数框以更改它们的填充颜色(例如黑色、白色、黑色、白色等)。

我知道使用全局变量不是最好的方法,但这是一个非常小的项目,我只想了解一些关于如何交替棋盘颜色的逻辑。非常感谢您的帮助!

canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");

var x, y,
  boxWidth = 30,
  boxHeight = 30;

for (x = 0; x < canvas.width; x += boxWidth) {
  for (y = 0; y < canvas.height; y += boxHeight) {
    ctx.beginPath();
    ctx.rect(x, y, boxWidth, boxHeight);
    // fill odd boxes
    (x/boxWidth + y/boxHeight) % 2 && ctx.fill()
    ctx.stroke();
    ctx.closePath();
  }
}
<canvas id="canvas" width="240" height="240"></canvas>

您可以使用 fillRect 这样做:

canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");

var x, y,
  boxWidth = 30,
  boxHeight = 30;

for (x = 0; x < canvas.width; x += boxWidth) {
  for (y = 0; y < canvas.height; y += boxHeight) {
    ctx.fillStyle = (x / boxWidth + y / boxHeight) % 2 === 0? "white": "black"; // determine which color to use depending on the index of x (x / boxWidth) an the index of y (y / boxHeight)
    ctx.fillRect(x, y, boxWidth, boxHeight);
  }
}
<canvas id="canvas" width="240" height="240"></canvas>

您也可以尝试只将您的值增加 1(而不是 boxWidth),这样可以更简单地检查它们是偶数还是奇数。然后你需要缩放或乘以 boxWidthboxHeight:

canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");

var x, y,
  boxWidth = 30,
  boxHeight = 30;

var numbRows = Math.floor(canvas.width / boxWidth),
  numbCols = Math.floor(canvas.height / boxHeight);

ctx.save();
ctx.scale(boxWidth, boxHeight);
for (x = 0; x < numbRows; x++) {
  for (y = 0; y < numbCols; y++) {
    if ((x+y) % 2 == 0) ctx.fillStyle = 'white';
    else ctx.fillStyle = 'black';
    ctx.beginPath();
    ctx.rect(x, y, boxWidth, boxHeight);
    ctx.stroke();
    ctx.closePath();
  }
}
ctx.restore();

for 循环的替代方法

另一种没有循环的方法,在顶角绘制 2 x 2 正方形的图案,然后通过将 canvas 复制到自身来重复此操作。

首先创建顶部 2 x 2 的正方形,然后用副本填充板的其余部分。

  1. 前2乘2到4乘2
  2. 然后 4 x 2 到 8 x 2
  3. 然后 8 x 2 到 8 x 4
  4. 然后 8 x 4 到 8 x 8

例子

const w= 100;
canvas.height = canvas.width = w * 8;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(0, 0, w + w, w + w);
ctx.fillStyle = "white";
ctx.fillRect(0, 0, w, w);
ctx.fillRect(w, w, w, w);
ctx.drawImage(canvas, 0, 0, w * 2, w * 2, w * 2, y    , w * 2, w * 2);    
ctx.drawImage(canvas, 0, 0, w * 4, w * 2, w * 4, y    , w * 4, w * 2);
ctx.drawImage(canvas, 0, 0, w * 8, w * 2, 0    , w * 2, w * 8, w * 2);
ctx.drawImage(canvas, 0, 0, w * 8, w * 4, 0    , w * 4, w * 8, w * 4);

因此,它在 7 次渲染调用中绘制,如果网格更大,则需要再调用 2 次,即 16 x 16,并且尺寸每增加一倍只需要再调用两次。

图案可以非常复杂,但不会像下一个具有阴影和不同复合调用的示例那样产生过多的渲染压力。

const squareSize = 72;
const boardSize = 8;
const borderSize = 8;
canvas.height = canvas.width = squareSize * boardSize + borderSize * 2;
const ctx = canvas.getContext("2d");
var x = borderSize;
var y = x;
var w = squareSize;
drawSquare(3, 3, canvas.width - 6, "black", "#F97"); 
drawSquare(x, y, w, "white", "#964");
drawSquare(w + x, y, w, "black", "#745");  
ctx.drawImage(canvas, x, y, w, w, x + w, y + w, w, w);
ctx.drawImage(canvas, x + w, y, w, w, x, y + w, w, w); 
ctx.drawImage(canvas, x, y, w * 2, w * 2, x + w * 2, y, w * 2, w * 2);    
ctx.drawImage(canvas, x, y, w * 4, w * 2, x + w * 4, y, w * 4, w * 2);
ctx.drawImage(canvas, x, y, w * 8, w * 2, x, y + w * 2, w * 8, w * 2);
ctx.drawImage(canvas, x, y, w * 8, w * 4, x, y + w * 4, w * 8, w * 4);
drawSquare(0,0,canvas.width,"rgba(0,0,0,0.0)","rgba(0,0,0,0.05)");

// done.

// this function is only called twice.
function drawSquare(x,y,size,color,color2){
  ctx.save();
  ctx.shadowColor = color2;
  ctx.shadowBlur = size * 0.2;
  ctx.shadowOffsetX = 0;
  ctx.shadowOffsetY = 0;
  ctx.beginPath();
  ctx.rect(x,y,size,size);
  ctx.clip();
  ctx.lineWidth = size;
  ctx.fillStyle = color;
  ctx.fillRect(x,y,size,size);
  ctx.globalAlpha = 0.5;
  ctx.strokeRect(x - size / 2,y - size / 2, size * 2, size * 2);
  ctx.shadowBlur = size * 0.5;
  ctx.strokeRect(x - size / 2,y - size / 2, size * 2, size * 2);
  ctx.shadowColor = "rgba(0,0,0,0)";
  ctx.shadowBlur = 0;
  ctx.globalAlpha = 1;
  ctx.strokeStyle = color2;
  ctx.lineWidth = 2;
  ctx.strokeRect(x+1,y+1,size -2,size-2);
  ctx.globalAlpha = 0.75;
  ctx.fillRect(x+1,y+1,size-2,size-2);
  ctx.globalCompositeOperation = "screen";
  ctx.fillStyle = "white";
  ctx.globalAlpha = 0.1;
  ctx.fillRect(x,y,4,size);
  ctx.fillRect(x,y,2,size);
  ctx.fillRect(x+4,y,size-4,4);
  ctx.fillRect(x+2,y,size-2,2);
  ctx.restore();
}
canvas { border : 2px solid black; }
<canvas id="canvas" ></canvas>

使用图案样式。

创建一个屏幕外 canvas 来保存顶部 2 x 2 的图案,绘制图案,然后将屏幕上 canvas 的 fillStyle 分配给创建的新图案从屏幕外 canvas 填充整个 canvas.

const w = 72;
const patCan = document.createElement("canvas");
patCan.height = patCan.width = w * 2;
var ctx = patCan.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(0, 0, w + w, w + w);
ctx.fillStyle = "white";
ctx.fillRect(0, 0, w, w);
ctx.fillRect(w, w, w, w);
// setup display canvas
canvas.height = canvas.width = w * 8;
var ctx = canvas.getContext("2d");
ctx.fillStyle = ctx.createPattern(patCan, "repeat");
ctx.fillRect(0, 0, w * 8, w * 8);
canvas { border : 8px solid green; }
<canvas id="canvas" ></canvas>

3行代码渲染

这里有一个可以用来画棋盘的巧妙小技巧:

var ctx = c.getContext("2d");

for(var x = 0; x < c.width; x += c.width / 4) ctx.fillRect(x, 0, c.width/8, c.height);
ctx.globalCompositeOperation = "xor";  // toggle alpha channel for every 2nd line
for(var y = 0; y < c.height; y += c.height / 4) ctx.fillRect(0, y, c.width, c.height/8);
<canvas id=c width=600 height=600></canvas>

我们使用 canvas' 大小来确定网格大小。你当然可以改变这个并偏移到你喜欢的任何东西。您仍然会使用具有实际宽度和高度的除数 4(2 个单元格)和 8(1 个单元格)。

第一步每隔一列绘制垂直的黑色条纹。然后我们使用切换 alpha 通道的“xor”复合模式知道默认颜色为黑色 (rgba(0,0,0,0)),每隔一行切换 alpha 通道。

请记住从一个空的 canvas 开始(由于需要重新绘制移动,您可能无论如何都是如此)并在绘制棋盘后将复合模式设置回“source-over”。

如果您想更改 颜色 本身,只需在最后添加一个额外的步骤:

ctx.globalCompositeOperation = "source-atop";  // will draw on top of what is filled
ctx.fillStyle = "#09a";
ctx.fillRect(0, 0, c.width, c.height);

fillStylefillRect()可以用图像、图案、渐变等替换或使用

要填充白色背景,只需使用复合模式“destination-over”(将在使用 alpha 通道填充的任何内容后面绘制),然后绘制背景。

另一种方法是在逐个填充每个单元格时使用切换开关:

var ctx = c.getContext("2d");
var toggle = false;

ctx.beginPath();
for(var y=0; y < c.height; y += c.height / 8) {
  toggle = !toggle;   // toggle for each row so they don't line up
  for(var x=0; x < c.width; x += c.width / 8) {
    // toggle for each cell and check, only draw if toggle = true
    if (toggle = !toggle) ctx.rect(x, y, c.width / 8, c.height / 8);
  }
}
ctx.fill();  // remember to use beginPath() for consecutive draw ops
<canvas id=c width=600 height=600></canvas>

访问逻辑

要知道您是否在单元格内,您只需计算鼠标相对于 canvas 的位置(请参阅 this answer 了解如何执行此操作)然后量化(在此处使用伪变量, 替换为真实的):

var cellSize = boardWidth / 8;   // assumes the board is 1:1 square

var pos = getMousePos(event);    // see linked answer above
var cellX = Math.floor(pos.x / cellSize) * cellSize; // start of current cell X
var cellY = Math.floor(pos.y / cellSize) * cellSize; // start of current cell Y

(要获取单元格索引,只需删除 * cellSize 部分)。

示例:

var ctx = c.getContext("2d"), x, y, w = c.width, h = c.height, cellSize = w / 8;
render();
ctx.lineWidth = 4; ctx.strokeStyle = "red"; ctx.setLineDash([7, 7]);

// non-optimized - in production only redraw when needed (cellX/Y changes)
c.onmousemove = function(e) {
  render();
  var cell = getCellPos(getMousePos(e));
  if (cell.x >= 0 && cell.x < w && cell.y >=0 && cell.y < h)
    ctx.strokeRect(cell.x + 2, cell.y + 2, cellSize - 4, cellSize - 4);
}

function getCellPos(pos) {
  return {x: Math.floor(pos.x / cellSize) * cellSize,
          y: Math.floor(pos.y / cellSize) * cellSize}
}
function getMousePos(e) {
  var rect = c.getBoundingClientRect();
  return {x: e.clientX-rect.x, y: e.clientY-rect.y}
}

function render() {
  ctx.clearRect(0, 0, w, h);
  for(x = 0; x < w; x += w>>2) ctx.fillRect(x, 0, cellSize, c.height);
  ctx.globalCompositeOperation = "xor";    // toggle alpha channel for every 2nd line
  for(y = 0; y < h; y += h>>2) ctx.fillRect(0, y, w, cellSize);
  ctx.globalCompositeOperation = "source-atop";       // fg
  ctx.fillStyle = "#3c4168";
  ctx.fillRect(0, 0, w, h);
  ctx.globalCompositeOperation = "destination-over";  // bg
  ctx.fillStyle = "#eee";
  ctx.fillRect(0, 0, w, h);
  ctx.globalCompositeOperation = "source-over";       // reset
}
body {background:#222;margin:20px 0 0 20px;}
<canvas id=c width=600 height=600></canvas>