Skip to content

Instantly share code, notes, and snippets.

@greggman
Last active Sep 23, 2022
Embed
What would you like to do?
Canvas Resize Method Comparison (via putImageData)
body {
margin: 0;
background-color: white;
}
.outer {
margin: 0 auto;
width: 80%;
}
canvas {
width: 100%;
height: 100px;
display: block;
}
.bad {
color: red;
font-weight: bold;
}
<div class="outer">
<div>using round(clientWidth * devicePixelRatio)</div>
<canvas id="use-clientWidth"></canvas>
<div>using round(getBoundingClientRect().width * devicePixelRatio)</div>
<canvas id="use-getBoundingClientRect"></canvas>
<div>using ResizeObserver (<span id="dpr-support"></span>)</div>
<canvas id="use-ResizeObserver"></canvas>
<div id="warning" style="color: red; display: none">(warning: no support for device-pixel-content-box)</div>
</div>
// Canvas Resize Method Comparison
// from https://webglfundamentals.org/webgl/webgl-resize-the-canvas-comparison.html
'use strict';
function setupCanvas(canvas, resizeFn) {
const ctx = canvas.getContext('2d');
const drawFn = (displayWidth, displayHeight) => {
canvas.width = displayWidth;
canvas.height = displayHeight;
const imgData = ctx.createImageData(displayWidth, displayHeight);
const u32View = new Uint32Array(imgData.data.buffer);
u32View.fill(0xFFFFFFFF);
for (let y = 0; y < displayHeight; y += 2) {
for (let x = 0; x < displayWidth / 2; ++x) {
u32View[y * displayWidth + x] = 0xFF000000;
}
}
for (let x = displayWidth / 2 | 0; x < displayWidth; x += 2) {
for (let y = 0; y < displayHeight; ++y) {
u32View[y * displayWidth + x] = 0xFF000000;
}
}
ctx.putImageData(imgData, 0, 0);
};
resizeFn(canvas, drawFn);
}
setupCanvas(document.querySelector('#use-clientWidth'), (canvas, drawFn) => {
const resizeAndDraw = () => {
const dpr = window.devicePixelRatio;
const displayWidth = Math.round(canvas.clientWidth * dpr);
const displayHeight = Math.round(canvas.clientHeight * dpr);
drawFn(displayWidth, displayHeight);
};
resizeAndDraw();
window.addEventListener('resize', resizeAndDraw);
});
setupCanvas(document.querySelector('#use-getBoundingClientRect'), (canvas, drawFn) => {
const resizeAndDraw = () => {
const dpr = window.devicePixelRatio;
const {width, height} = canvas.getBoundingClientRect();
const displayWidth = Math.round(width * dpr);
const displayHeight = Math.round(height * dpr);
drawFn(displayWidth, displayHeight);
};
resizeAndDraw();
window.addEventListener('resize', resizeAndDraw);
});
setupCanvas(document.querySelector('#use-ResizeObserver'), (canvas, drawFn) => {
const canvasToDisplaySizeMap = new Map([[canvas, [300, 150]]]);
const dprSupportElem = document.querySelector('#dpr-support');
const resizeAndDraw = () => {
const [displayWidth, displayHeight] = canvasToDisplaySizeMap.get(canvas);
drawFn(displayWidth, displayHeight);
};
function onResize(entries) {
for (const entry of entries) {
let width;
let height;
let dpr = window.devicePixelRatio;
let dprSupport = false;
if (entry.devicePixelContentBoxSize) {
// NOTE: Only this path gives the correct answer
// The other paths are an imperfect fallback
// for browsers that don't provide anyway to do this
width = entry.devicePixelContentBoxSize[0].inlineSize;
height = entry.devicePixelContentBoxSize[0].blockSize;
dpr = 1; // it's already in width and height
dprSupport = true;
} else if (entry.contentBoxSize) {
if (entry.contentBoxSize[0]) {
width = entry.contentBoxSize[0].inlineSize;
height = entry.contentBoxSize[0].blockSize;
} else {
// legacy
width = entry.contentBoxSize.inlineSize;
height = entry.contentBoxSize.blockSize;
}
} else {
// legacy
width = entry.contentRect.width;
height = entry.contentRect.height;
}
const displayWidth = Math.round(width * dpr);
const displayHeight = Math.round(height * dpr);
canvasToDisplaySizeMap.set(entry.target, [displayWidth, displayHeight]);
resizeAndDraw();
dprSupportElem.textContent = dprSupport ? "dpr supported 😀" : "dpr not supported 😢";
dprSupportElem.classList.toggle('bad', !dprSupport);
}
}
resizeAndDraw();
const resizeObserver = new ResizeObserver(onResize);
resizeObserver.observe(canvas, {box: 'content-box'});
});
{"name":"Canvas Resize Method Comparison (via putImageData)","settings":{},"filenames":["index.html","index.css","index.js"]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment