What DPR means
Device pixel ratio is the relationship between CSS pixels and physical display pixels. A DPR of 2 means one CSS pixel can be represented by roughly two physical pixels in each direction, which is common on many high-DPI screens.
Why DPR matters for images
Images that look sharp at DPR 1 can appear soft on high-DPI displays if only one low-resolution asset is served. Responsive images, srcset, and 2x or 3x image assets help browsers choose sharper files when needed.
Why fractional DPR values happen
Values such as 1.25 or 1.5 are common on desktop systems with display scaling or browser zoom. JavaScript cannot always separate browser zoom from OS scaling, so the result should be treated as the effective browser/device scaling context.
How to use DPR in QA
Include DPR when reporting screenshots, blurry icons, canvas rendering issues, or layout differences across devices. It helps explain why a high-resolution display can still have a smaller CSS viewport width.
DPR examples
DPR values are browser-reported scaling context. Treat them together with viewport width instead of using them alone.
DPR 1
One CSS pixel maps closely to one physical pixel.
DPR 2
High-DPI screens may need 2x images for sharp assets.
DPR 1.25 / 1.5
Often appears with OS display scaling or browser zoom.
Run a canvas and responsive-image example
Save this complete example as an HTML file and open it in a browser. The canvas keeps a 120 × 120 CSS-pixel box while its backing bitmap follows DPR. The image uses generated 1x, 2x, and 3x PNG candidates, so no external image files are required. Change page zoom, then press Update; the browser chooses the image candidate and may reuse a cached larger candidate.
<!doctype html>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>DPR canvas and image test</title>
<canvas id="sample" style="width:120px;height:120px" aria-label="Canvas density sample"></canvas>
<img id="photo" width="120" height="120" alt="Generated responsive-image density sample">
<button id="update" type="button">Update</button>
<pre id="readings"></pre>
<script>
const canvas = document.querySelector('#sample');
const image = document.querySelector('#photo');
function paint(target, density) {
target.width = target.height = Math.round(120 * density);
const ctx = target.getContext('2d');
ctx.setTransform(target.width / 120, 0, 0, target.height / 120, 0, 0);
ctx.fillStyle = '#e0f2fe';
ctx.fillRect(0, 0, 120, 120);
ctx.fillStyle = '#0f172a';
ctx.font = '16px sans-serif';
ctx.fillText(`${density}x bitmap`, 8, 62);
}
const candidates = [1, 2, 3].map(density => {
const bitmap = document.createElement('canvas');
paint(bitmap, density);
return { url: bitmap.toDataURL('image/png'), density };
});
image.srcset = candidates.map(item => `${item.url} ${item.density}x`).join(', ');
image.src = candidates[0].url;
function update() {
const dpr = window.devicePixelRatio || 1;
paint(canvas, dpr);
document.querySelector('#readings').textContent = JSON.stringify({
dpr, cssWidth: canvas.getBoundingClientRect().width,
bitmapWidth: canvas.width, viewportWidth: window.innerWidth,
visualScale: window.visualViewport?.scale ?? null,
selectedImageDensity: candidates.find(item => item.url === image.currentSrc)?.density ?? null
}, null, 2);
}
document.querySelector('#update').addEventListener('click', update);
image.addEventListener('load', update);
window.addEventListener('resize', update);
window.visualViewport?.addEventListener('resize', update);
update();
</script>
At DPR 2, expect a 240 × 240 backing bitmap inside the unchanged 120 × 120 CSS box. For production photos, export separate raster files at those densities and use the same srcset pattern. Page zoom can change DPR; pinch zoom changes visual scale without changing DPR. This test reports browser behavior, not a measurement of panel PPI.
Sources, checked September 5, 2026: MDN devicePixelRatio and zoom behavior; MDN responsive-image density candidates.