Crisp Canvas Rendering on High-DPI Screens

Fix blurry HTML5 Canvas rendering on Retina and HiDPI displays by scaling the canvas buffer to match the device pixel ratio. Includes step-by-step code examples.

Practical

Detailed Explanation

The Blurry Canvas Problem

By default, a <canvas> element's internal bitmap resolution matches its CSS size. On a DPR 2 display, a 300×150 canvas is upscaled to 600×300 physical pixels, causing blurriness.

The Fix

Scale the canvas buffer to match the device pixel ratio:

function setupHiDPICanvas(canvas, width, height) {
  const dpr = window.devicePixelRatio || 1;

  // Set the canvas buffer size (actual pixels)
  canvas.width = width * dpr;
  canvas.height = height * dpr;

  // Set the CSS display size
  canvas.style.width = width + 'px';
  canvas.style.height = height + 'px';

  // Scale the drawing context
  const ctx = canvas.getContext('2d');
  ctx.scale(dpr, dpr);

  return ctx;
}

// Usage
const canvas = document.getElementById('myCanvas');
const ctx = setupHiDPICanvas(canvas, 300, 150);
// Now draw at CSS pixel coordinates - everything will be crisp
ctx.fillRect(10, 10, 100, 50);

Before and After

Standard HiDPI-Aware
Canvas buffer 300×150 600×300
CSS size 300×150 300×150
Physical pixels 600×300 600×300
Quality Blurry (upscaled) Crisp (1:1 mapping)

Handling DPR Changes

If the user drags the window to a monitor with a different DPR:

function watchDPRAndResize(canvas, drawFunction) {
  let currentDPR = window.devicePixelRatio;

  const check = () => {
    if (window.devicePixelRatio !== currentDPR) {
      currentDPR = window.devicePixelRatio;
      setupHiDPICanvas(canvas,
        parseInt(canvas.style.width),
        parseInt(canvas.style.height)
      );
      drawFunction();
    }
    requestAnimationFrame(check);
  };
  check();
}

WebGL Considerations

For WebGL, set the drawing buffer size:

const dpr = window.devicePixelRatio || 1;
canvas.width = canvas.clientWidth * dpr;
canvas.height = canvas.clientHeight * dpr;
gl.viewport(0, 0, canvas.width, canvas.height);

Performance Trade-Off

A DPR 3 canvas has 9× more pixels than DPR 1. For animation-heavy canvases, consider capping at DPR 2:

const dpr = Math.min(window.devicePixelRatio || 1, 2);

Use Case

Developers building canvas-based applications (charts, games, drawing tools) need DPR-aware rendering to prevent blurry output on modern high-DPI screens.

Try It — Screen Info Display

Open full tool