Why does this code make my visual studio live preview and browser go mad when I change the width or height of canvas? Sometimes it works after refreshing page a few times or closing and opening live preview but I can't get why it makes everything go crazy.
When I put height and width and the same ratio of 400 everything works but when I change it for example for 300 for both it goes crazy. It should have worked for any height or width.
<!doctype html>
<html>
<head>
<style>
canvas {
border: 10px solid rgb(0, 0, 0);
border-radius: 3em;
padding: 40px;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="400" height="400"></canvas>
<script>
document.addEventListener("DOMContentLoaded", function () {
function drawPixels(ctx, x1, y1, x2, y2, pixelSize, color) {
const dx = Math.abs(x2 - x1);
const dy = Math.abs(y2 - y1);
const sx = x1 < x2 ? 1 : -1;
const sy = y1 < y2 ? 1 : -1;
let err = dx - dy;
let x = x1;
let y = y1;
while (true) {
ctx.fillStyle = color;
ctx.fillRect(x, y, pixelSize, pixelSize);
if (x === x2 && y === y2) break;
const err2 = 2 * err;
if (err2 > -dy) {
err -= dy;
x += sx * pixelSize;
}
if (err2 < dx) {
err += dx;
y += sy * pixelSize;
}
}
}
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var pixelSize = 20;
var rectWidth = canvas.width;
var rectHeight = canvas.height;
canvas.height += pixelSize;
var sq = Math.min(rectHeight, rectWidth);
console.log(sq);
var x1 = 0;
var y1 = sq;
var x2 = sq / 2;
var y2 = 0;
var x3 = sq;
var y3 = sq;
drawPixels(ctx, x1, y1, x2, y2, pixelSize, "red");
drawPixels(ctx, x2, y2, x3, y3, pixelSize, "red");
drawPixels(ctx, x3, y3, x1, y1, pixelSize, "green");
});
</script>
</body>
</html>
The canvas isn't crashing the browser, the while
loop is. JS is single-threaded, which means your code needs to yield to the browser so it can repaint and process interactions. If you have code that blocks the repaint for less than a hundred milliseconds or so, the user may not notice. If you have code that blocks the rendering loop for more than a few seconds, the page might hang or be considered unresponsive by the browser. If you have code that never lets a repaint run, perhaps due to an infinite loop, the page will never become responsive again and will probably run your CPU at 100% until the page is forceably killed. The last case is happening here.
How do debug this? One way is to check your loop termination condition:
// this is one thing to look for when your page doesn't respond
while (true) {
// add this so you can understand why `break` is unreachable
console.log({x, x2, y, y2});
// this stops the loop, but is never true for 300x400
if (x === x2 && y === y2) break;
}
If I open the dev tools and run your page, I see the console spammed with crazy values like { x: 477400, x2: 150, y: -954500, y2: 0 }
, clearly not what you intended to happen.
It seems drawPixels
is a line-drawing algorithm of some sort, probably Bresenham's, but your implementation isn't accounting for step sizes that might overshoot the destination. You could isolate exactly when the overshoot happens by checking when x
and x2
and y
and y2
are fairly close rather than perfectly equal. Examining the values at this critical moment of failure could be useful.
To make debugging easier, consider using a single-step debugger, or converting your while (true)
loop temporarily to a finite loop with plenty of room for the algorithm to complete:
// if 10_000 spams too much, you can decrease this number and run
// the code again to stop the algorithm at a critical point
let tries = 10_000;
while (tries--) {
// ... your algorithm and debugging logs here
}
if (tries <= 0) {
console.log("infinite loop detected");
}
If you're working from a known algorithm, possibly provided as pseudocode, now is a good time to double-check that you've translated it correctly.
Now that you know what the problem is and you've gained some techniques for isolating the problem and debugging small programs, you're equipped to fix the bug and complete the algorithm you're trying to implement.
If you're not trying to learn algorithms and just want to draw a line, then consider using ctx.lineTo()
rather than reinventing the wheel.