Three.js Cheatsheet

Resizing and Responsiveness

Use this Three.js reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

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 changing aspect, fov, near, or far. 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 by setPixelRatio() — the canvas CSS size is w × h, the physical resolution is w*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: 0 or no defined block context), clientWidth / clientHeight return 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. Use webkitRequestFullscreen or 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);
});