Three.js Cheatsheet
Resizing and Responsiveness
Use this Three.js reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Core Resize Handler
function onResize() { const w = window.innerWidth; const h = window.innerHeight; // Update camera camera.aspect = w / h; camera.updateProjectionMatrix(); // REQUIRED after changing aspect/fov/near/far // Update renderer renderer.setSize(w, h); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); } window.addEventListener('resize', onResize);
Always call
camera.updateProjectionMatrix()after changingaspect,fov,near, orfar. Forgetting this is the most common resize bug.
Canvas-Fit Patterns
Full Window (most common)
// CSS // body { margin: 0; overflow: hidden; } // canvas { display: block; } renderer.setSize(window.innerWidth, window.innerHeight);
Fit a Container div
const container = document.getElementById('canvas-container'); function onResize() { const w = container.clientWidth; const h = container.clientHeight; camera.aspect = w / h; camera.updateProjectionMatrix(); renderer.setSize(w, h); } const resizeObserver = new ResizeObserver(onResize); resizeObserver.observe(container); // Initial size onResize();
Fixed Aspect Ratio (e.g., always 16:9)
const targetAspect = 16 / 9; function onResize() { let w = window.innerWidth; let h = window.innerWidth / targetAspect; if (h > window.innerHeight) { h = window.innerHeight; w = window.innerHeight * targetAspect; } renderer.setSize(w, h); // camera.aspect stays fixed camera.aspect = targetAspect; camera.updateProjectionMatrix(); // Center the canvas renderer.domElement.style.marginLeft = ((window.innerWidth - w) / 2) + 'px'; renderer.domElement.style.marginTop = ((window.innerHeight - h) / 2) + 'px'; }
Pixel Ratio (DPR / Retina)
// Cap at 2 — above 2 is wasteful on modern displays renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // Get current DPR const dpr = renderer.getPixelRatio(); // Physical pixel size of the canvas const physicalW = Math.floor(window.innerWidth * dpr); const physicalH = Math.floor(window.innerHeight * dpr);
renderer.setSize(w, h)automatically factors in the pixel ratio set bysetPixelRatio()— the canvas CSS size isw × h, the physical resolution isw*dpr × h*dpr.
Orthographic Camera Resize
// Re-derive the frustum from the current window size function onResize() { const aspect = window.innerWidth / window.innerHeight; const frustumH = 10; // keep a fixed world-space height camera.left = -frustumH * aspect / 2; camera.right = frustumH * aspect / 2; camera.top = frustumH / 2; camera.bottom = -frustumH / 2; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }
Render Targets on Resize
// WebGLRenderTarget must be explicitly resized const rt = new THREE.WebGLRenderTarget( window.innerWidth * window.devicePixelRatio, window.innerHeight * window.devicePixelRatio ); function onResize() { const w = window.innerWidth; const h = window.innerHeight; const dpr = Math.min(window.devicePixelRatio, 2); renderer.setSize(w, h); renderer.setPixelRatio(dpr); rt.setSize(w * dpr, h * dpr); // must match renderer physical size camera.aspect = w / h; camera.updateProjectionMatrix(); }
Post-Processing (EffectComposer) on Resize
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; const composer = new EffectComposer(renderer); composer.addPass(new RenderPass(scene, camera)); function onResize() { const w = window.innerWidth; const h = window.innerHeight; camera.aspect = w / h; camera.updateProjectionMatrix(); renderer.setSize(w, h); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); composer.setSize(w, h); // also resize composer and all passes composer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); }
Demand Rendering (Only Render on Change)
Save GPU on idle scenes by rendering only when something changes.
let renderRequested = false; function requestRender() { if (!renderRequested) { renderRequested = true; requestAnimationFrame(render); } } function render() { renderRequested = false; renderer.render(scene, camera); } // Trigger on control change controls.addEventListener('change', requestRender); // Trigger on resize window.addEventListener('resize', () => { onResize(); requestRender(); }); // Initial render requestRender();
Fullscreen API
function toggleFullscreen() { if (!document.fullscreenElement) { renderer.domElement.requestFullscreen(); } else { document.exitFullscreen(); } } // Handle resize when entering/exiting fullscreen document.addEventListener('fullscreenchange', onResize); // Keyboard shortcut window.addEventListener('keydown', (e) => { if (e.code === 'KeyF') toggleFullscreen(); });
Checking Renderer Size
const size = new THREE.Vector2(); renderer.getSize(size); console.log(size.x, size.y); // CSS size in logical pixels // Physical size (factoring DPR) const physW = Math.round(size.x * renderer.getPixelRatio()); const physH = Math.round(size.y * renderer.getPixelRatio());
ResizeObserver vs window resize
// window resize fires for any window size change window.addEventListener('resize', onResize); // ResizeObserver fires for a specific element (preferred for canvas in a div) const observer = new ResizeObserver((entries) => { for (const entry of entries) { const { width, height } = entry.contentRect; camera.aspect = width / height; camera.updateProjectionMatrix(); renderer.setSize(width, height); } }); observer.observe(renderer.domElement.parentElement);
Common Responsive Gotchas
Forgot
updateProjectionMatrix()— the most common bug. Camera projection is cached and does not update automatically.
Canvas is 0×0 — if the parent element has no explicit size (e.g.,
height: 0or no defined block context),clientWidth/clientHeightreturn 0. Ensure the container has a defined height via CSS.
Blurry canvas — forgetting
setPixelRatio()or setting it to 1 on a Retina/HiDPI display makes the canvas render at half resolution.
DPR > 2 wastes GPU — cap at 2.
Math.min(window.devicePixelRatio, 2)is the standard pattern.
Render target size mismatch — post-processing composers and render targets must be resized manually (they don't track the renderer size automatically).
Fullscreen on iOS —
requestFullscreen()is not supported on iOS Safari. UsewebkitRequestFullscreenor handle it differently for mobile.
> Debounce heavy resize operations — if resizing triggers expensive work (rebuilding textures, etc.), debounce the handler:
let resizeTimer; window.addEventListener('resize', () => { clearTimeout(resizeTimer); resizeTimer = setTimeout(onResize, 100); });