Reputation: 2081
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
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