Canvas resolution and the pixel mismatch problem
A <canvas> element has two independent sizes: the number of pixels you can draw into, set via its width and height attributes, and its on-screen display size, which is governed by CSS. Nothing forces these two to line up. When they diverge, one canvas pixel can cover several physical screen pixels, or only a fraction of one, producing blurry edges and other rendering artifacts.
On high-density displays the situation gets worse because CSS pixels are not the same as physical screen pixels. CSS pixels are an abstract unit tied to a nominal 96 DPI display; the browser maps them to the monitor's actual pixels using the global devicePixelRatio value. A dPR of 2 means one CSS pixel covers exactly two physical pixels. Zooming complicates this further, since the browser adjusts the reported dPR to fractional values like 2.65.
For crisp graphics you often want an exact one-to-one mapping between canvas pixels and physical screen pixels — a state usually called "pixel-perfect" rendering. The long-standing workaround is to read the element's size with getBoundingClientRect(), multiply by devicePixelRatio, round the result, and then scale the canvas via CSS:
<style>
/* … styles that affect the canvas' size … */
</style>
<canvas id="myCanvas"></canvas>
<script>
const cvs = document.querySelector('#myCanvas');
// Get the canvas' size in CSS pixels
const rectangle = cvs.getBoundingClientRect();
// Convert it to real pixels. Ish.
cvs.width = rectangle.width * devicePixelRatio;
cvs.height = rectangle.height * devicePixelRatio;
// Start drawing…
</script>
That approach works only when dPR and the element's final size are integers. In practice neither is guaranteed. An element positioned with margin-left: 33% can easily resolve to a fractional CSS pixel value, and on a display with dPR 2.65 every calculation ends in a fraction.
What pixel snapping actually does
Browsers handle fractional pixel values through a process called pixel snapping. The browser rounds a fractional CSS dimension to a whole number of physical pixels before painting. The exact rounding rule differs by browser: for an element with a CSS width of 791.984px on a dPR 1 display, one engine may paint it 792 physical pixels wide while another uses 791. A difference of one pixel is invisible for most content, but for tightly aligned graphics it causes blurriness or visible interference patterns such as the Moiré effect.
This leaves developers without a reliable way to know the element's true size in physical pixels at paint time. That is precisely the gap devicePixelContentBox fills.
Observing physical pixel dimensions
Since Chrome 84, ResizeObserver supports a new box measurement: devicePixelContentBox. It reports the observed element's content box in physical pixels and lets you adjust a canvas' backing store before the browser paints.
ResizeObserver callbacks run after layout and just before paint. If you observe an element with the option box: 'device-pixel-content-box', the callback fires with an entry whose devicePixelContentBoxSize reflects the exact physical pixel count. You then resize the canvas buffer to match:
const observer = new ResizeObserver((entries) => {
const entry = entries.find((entry) => entry.target === canvas);
canvas.width = entry.devicePixelContentBoxSize[0].inlineSize;
canvas.height = entry.devicePixelContentBoxSize[0].blockSize;
/* … render to canvas … */
});
observer.observe(canvas, {box: ['device-pixel-content-box']});
Because the size is provided in whole physical pixels, there is no fractional rounding and no need to guess with getBoundingClientRect(). Canvas resizing, repositioning and even animation no longer produce Moiré patterns on the rendering. The box option also steers which size is being observed: each entry always carries borderBoxSize, contentBoxSize and devicePixelContentBoxSize, but the callback fires only when an observed metric changes. Browser support is limited to Chrome 84 and later, with Safari 13.1+ covering the base ResizeObserver API.
Detecting support
Feature detection is straightforward: observe any element and check for the property on the entry passed to the callback. If devicePixelContentBoxSize is missing, fall back to the older fractional-prone calculation.
function hasDevicePixelContentBox() {
return new Promise((resolve) => {
const ro = new ResizeObserver((entries) => {
resolve(entries.every((entry) => 'devicePixelContentBoxSize' in entry));
ro.disconnect();
});
ro.observe(document.body, {box: ['device-pixel-content-box']});
}).catch(() => false);
}
if (!(await hasDevicePixelContentBox())) {
// The browser does NOT support devicePixelContentBox
}
Physical pixels were previously unknowable from script after layout. With devicePixelContentBox, a canvas can be sized to match the screen exactly, giving pixel-perfect text and graphics on any display — with integer or fractional dPR alike.



