Spot the bug - #172: Retina Canvas Renderer

Why are these radar blips scaling up all wrong on resize?

function setupRadarDisplay(canvas, blips) {
  const ctx = canvas.getContext('2d');
  const dpr = window.devicePixelRatio || 1;
  const rect = canvas.getBoundingClientRect();

  canvas.width = Math.floor(rect.width * dpr);
  canvas.height = Math.floor(rect.height * dpr);
  ctx.scale(dpr, dpr);

  function render() {
    ctx.clearRect(0, 0, rect.width, rect.height);
    ctx.lineWidth = 1.5;
    ctx.strokeStyle = '#38bdf8';

    for (const blip of blips) {
      ctx.save();
      ctx.translate(blip.x, blip.y);
      ctx.beginPath();
      ctx.arc(0, 0, blip.radius, 0, Math.PI * 2);
      ctx.stroke();
      ctx.restore();
    }
  }

  return { render };
}

Reply with what is broken and how you would fix it.

The clearRect call is using the unscaled rect.width and rect.height.

This means it’s clearing an area smaller than the actual canvas resolution. You would need to scale those dimensions by dpr inside the clearRect function.

ctx.clearRect(0, 0, rect.width * dpr, rect.height * dpr);

Spot the Bug answer: The clearRect operation uses the unscaled rect dimensions instead of the scaled canvas dimensions.

The fix:

Change ctx.clearRect(0, 0, rect.width, rect.height); to ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);

Why:
After ctx.scale(dpr, dpr) is called, all subsequent drawing operations, including clearRect, are affected by the scaling. To clear the entire visible (scaled) area, clearRect needs to be called with the original logical dimensions, which are canvas.width / dpr and canvas.height / dpr, not the raw rect.width and rect.height which are already implicitly scaled by the dpr in the canvas.width/height assignment.

First-answer leaderboard

  1. @emmawalter5 - 11 (firsts) :trophy:
  2. @kirupa - 8 (firsts)
  3. @Apexcodes - 6 (firsts)
  4. @adnanahmed - 2 (firsts)