Skip to content

Instantly share code, notes, and snippets.

@greggman
Last active September 23, 2022 20:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save greggman/efa3ba6ee0984684340ba0ee0e2af779 to your computer and use it in GitHub Desktop.
Save greggman/efa3ba6ee0984684340ba0ee0e2af779 to your computer and use it in GitHub Desktop.
Canvas Resize Method Comparison (via WebGL)
body {
margin: 0;
background-color: white;
}
.outer {
margin: 0 auto;
width: 80%;
}
canvas {
width: 100%;
height: 100px;
display: block;
background-color: red;
}
.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 class
<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
function setupCanvas(canvas, resizeFn) {
const gl = canvas.getContext('webgl2', {antialias: false});
const createShader = (gl, type, src) => {
const sh = gl.createShader(type);
gl.shaderSource(sh, src);
gl.compileShader(sh);
console.log(gl.getShaderInfoLog(sh));
return sh;
};
const prg = gl.createProgram();
gl.attachShader(prg, createShader(gl, gl.VERTEX_SHADER, `#version 300 es
void main() {
ivec2 unitQuad = ivec2(
gl_VertexID / 6 + gl_VertexID % 2,
(gl_VertexID / 2 + gl_VertexID / 3) % 2);
gl_Position = vec4(vec2(unitQuad) * 2.0 - 1.0, 0, 1);
}
`));
gl.attachShader(prg, createShader(gl, gl.FRAGMENT_SHADER, `#version 300 es
precision highp float;
uniform vec2 resolution;
out vec4 fragColor;
void main() {
vec2 odd = floor(mod(gl_FragCoord.xy, vec2(2)));
vec4 hStripe = mix(vec4(0,0,0,1), vec4(1), odd.y);
vec4 vStripe = mix(vec4(0,0,0,1), vec4(1), odd.x);
fragColor = mix(hStripe, vStripe, step(resolution.x / 2.0, gl_FragCoord.x));
}
`));
gl.linkProgram(prg);
gl.useProgram(prg);
const resolutionLoc = gl.getUniformLocation(prg, 'resolution');
const drawFn = (displayWidth, displayHeight) => {
canvas.width = displayWidth;
canvas.height = displayHeight;
gl.viewport(0, 0, displayWidth, displayHeight);
gl.uniform2f(resolutionLoc, displayWidth, displayHeight);
gl.drawArrays(gl.TRIANGLES, 0, 6);
};
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 WebGL)","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