How can I change the fill colors of each checkerboard window in JS using Canvas?

I just thought about drawing a checkerboard using JS and Canvas, and I have this code that draws boxes fine with loops 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>
      

Run codeHide result


Now I am wondering how I can access each odd field along the axes to change their fill colors (e.g. black, white, black, white, etc.).

I know that using globals is not the best way, but this is a very small project and I just want some logic on how I can alternate the colors of the checkerboard. Your help is greatly appreciated!

+3


source to share


5 answers


You can also try to only increase your values ​​by 1, which makes it easier to check if they are even or odd. Then you need to either scale or multiply by boxWidth

and boxHeight

:



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

      

+3


source


You can use fillRect

to do it like this:



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>
      

Run codeHide result


+1


source


Rendering with 3 lines of code

Here's a neat little trick you can use to draw a checkerboard:

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>
      

Run codeHide result


We use the canvas size to determine the size of the grid. You can, of course, change this and compensate for whatever you want. You will still be using 4 (2 cells) and 8 (1 cell) divisors with the actual width and height.

The first step is drawing vertical black stripes every other column. We then switch the alpha channel for every other line, knowing the default color is black ( rgba(0,0,0,0)

), using the "xor" composite mode, which switches the alpha channel.

Remember to start with a blank canvas (which you probably do anyway because of the need to redraw the movements), and to return the composite mode to "original" after drawing the board.

If you want to change the color itself, just add an extra step at the end:

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

      

fillStyle

and fillRect()

can be replaced or used with an image, pattern, gradient, etc.

To fill a white background, just use the "destination-over" composite mode (will paint behind anything filled with an alpha channel), then paint for the background.

An alternative is to use a toggle switch each time each cell is filled:

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>
      

Run codeHide result


Access logic

To find out if you are inside a cell, you simply calculate the position of the mouse relative to the canvas (see this answer for how to do that) and then quantize (using pseudo variables here, replace with real):

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

      

(to get the cell index, just drop the part * cellSize

).

Example:

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>
      

Run codeHide result


+1


source


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>
      

Run codeHide result


0


source


For loop alternatives

Another way without a loop, draw a 2 by 2 squares pattern in the upper corner, then repeat this by copying the canvas onto you.

First create the top 2 by 2 squares, then fill in the rest of the board with copies.

  • First 2 by 2 by 4 by 2
  • then 4 by 2 - 8 by 2
  • then 8 by 2 - 8 by 4
  • then 8 by 4 - 8 by 8

Example

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

      

So it gets drawn in 7 render calls if the grid was larger than 2 more calls for 16 by 16 and two more calls are required for each doubling in size.

The template can be very complex, but not over-stressing the render like the following example with shadows and various composite calls.

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>
      

Run codeHide result


Use a template style.

Create a screen canvas to hold the template at the top 2 by 2, draw the template, then assign fillStyle

on the screen canvas a new template created on the off-screen canvas and fill the entire 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>
      

Run codeHide result


0


source







All Articles