jowo
jowo

Reputation: 2081

HTML5 Canvas stroke() changing color between draws

I've created a very simple JavaScript snippet to illustrate a weird HTML5 canvas behavior that I have been experiencing.

I keep drawing the same set of strokes, just in a different order, every 100ms. Why is it that the color of some of the strokes keeps changing? It only happens when I shuffle the draw order between calls, even though the lines are drawn in the same location and same color each frame.

const canvasWidth = 500;
const gapBetweenLines = 5;
const nbrLines = canvasWidth / gapBetweenLines;
const canvasHeight = 500;

const canvas = document.getElementById('map');
canvas.width = canvasWidth;
canvas.height = canvasHeight;

// create an array of line objects, each with a with random color
let lines = [];
for (let i = 0; i < nbrLines; i++) {
  lines.push({
    index: i,
    x: i * gapBetweenLines,
    color: '#' + Math.floor(Math.random() * 16777215).toString(16)
  });
}

// function to shuffle the given array in place
function shuffle(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
}

// draw lines on the canvas at specific intervals with the random colors
function drawLines() {
  const shuffledLines = [...lines];
  shuffle(shuffledLines);

  let ctx = canvas.getContext('2d');
  for (let i = 0; i < nbrLines; i++) {
    const line = shuffledLines[i];
    ctx.strokeStyle = line.color;
    // ctx.save();
    ctx.beginPath();
    ctx.moveTo(line.x, 0);
    ctx.lineTo(line.x, canvasHeight);
    ctx.stroke();
    // ctx.restore();
  }
}

// call the drawLines function every 100ms
setInterval(drawLines, 100);
<!DOCTYPE html>
<html>
  <body>
    <h1>Flickering Lines</h1>
    <canvas id="map"></canvas>
    <div id="lineinfo"></div>
  </body>
</html>

The past day had me spending an embarrassing amount of time trying to narrow down the cause of this. Is there something about HTML5 Canvas drawing that I simply misunderstand?

Saving and restoring the context between each stroke does not make any difference.

Upvotes: 0

Views: 203

Answers (1)

Kaiido
Kaiido

Reputation: 136678

The problem is in your color generation.

color: '#' + Math.floor(Math.random() * 16777215).toString(16)

Number#toString(16) does not pad the returned string with zeroes:

console.log(12..toString(16)) // "c", not "0C"

This means that in your code, some of your lines may have their color property set to values that aren't a valid HEX format (e.g a five or two chars HEX is invalid).

To fix that, you can force your color generator to be always 6 length by padding as many zeroes as needed using the String#padStart() method.

const canvasWidth = 500;
const gapBetweenLines = 5;
const nbrLines = canvasWidth / gapBetweenLines;
const canvasHeight = 500;

const canvas = document.getElementById('map');
canvas.width = canvasWidth;
canvas.height = canvasHeight;

// create an array of line objects, each with a with random color
let lines = [];
for (let i = 0; i < nbrLines; i++) {
  lines.push({
    index: i,
    x: i * gapBetweenLines,
    color: '#' + Math.floor(Math.random() * 16777215).toString(16)
      // force always 6 length
      .padStart(6, "0")
  });
}

// function to shuffle the given array in place
function shuffle(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
}

// draw lines on the canvas at specific intervals with the random colors
function drawLines() {
  const shuffledLines = [...lines];
  shuffle(shuffledLines);

  let ctx = canvas.getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  for (let i = 0; i < nbrLines; i++) {
    const line = shuffledLines[i];
    ctx.strokeStyle = line.color;
    // ctx.save();
    ctx.beginPath();
    ctx.moveTo(line.x, 0);
    ctx.lineTo(line.x, canvasHeight);
    ctx.stroke();
    // ctx.restore();
  }
}

// call the drawLines function every 100ms
setInterval(drawLines, 100);
<!DOCTYPE html>
<html>
  <body>
    <h1>Flickering Lines</h1>
    <canvas id="map"></canvas>
    <div id="lineinfo"></div>
  </body>
</html>

Upvotes: 1

Related Questions