Capítulo 832 de 859

Chapter 832: Multiple Canvases, Multiple Scenes (Manual)

Core Idea

Instead of creating one WebGL canvas/renderer per 3D diagram on a page (which runs into WebGL context limits and resource-sharing problems), use a single full-viewport canvas and one Scene per "virtual canvas" element, rendering each scene into the screen-space rectangle of its corresponding DOM element via the scissor/viewport.

Key Concepts

  • Why not one canvas per diagram: many small canvases each need their own WebGL context, and browsers cap the number of live contexts, plus resources aren't easily shared across separate renderers.
  • One shared canvas + scissor test: a single full-screen canvas (z-index: -1) sits behind the page; regular DOM elements mark where each "virtual canvas" should appear.
  • renderer.setScissorTest, setViewport, setScissor: restrict rendering to the on-screen rectangle matching each marker element before rendering that element's scene.
  • Scroll lag trade-off: a position: fixed canvas doesn't scroll with the page, so slow renders visibly lag behind scrolled content; switching the canvas to position: absolute and repositioning it via a transform on each render lets it scroll with the page, trading a possible edge glitch for no mid-page slip.
  • Generic scene registry: a list of {elem, fn} pairs (or a name→init-function map driven by HTML dataset attributes) lets the main render loop stay agnostic of what each scene actually draws, only rendering scenes whose element is currently visible on screen.
  • Per-scene controls: adding e.g. TrackballControls to an individual scene just means constructing it with that scene's own camera and DOM element, and updating it inside that scene's render function.
  • Alternative: offscreen render + 2D copy: render each scene to an offscreen canvas and blit the result into a normal 2D <canvas> per element — more compositing flexibility (real HTML elements instead of one shared background canvas) at the cost of an extra copy per area.

Code Examples

function makeScene(elem) {
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(45, 2, 0.1, 5);
  camera.position.set(0, 1, 2);
  camera.lookAt(0, 0, 0);
  scene.add(new THREE.DirectionalLight(0xffffff, 1));
  return { scene, camera, elem };
}
  • What it demonstrates: a factory for one independent scene+camera+light setup, associated with one DOM marker element.

Key Takeaways

  1. Prefer one shared canvas with per-element scissor/viewport rendering over one WebGL context per diagram — it avoids context limits and resource-sharing headaches.
  2. position: fixed keeps the shared canvas visually static during scroll (causing lag on slow frames); position: absolute with a per-frame transform lets it scroll with the page instead.
  3. A generic list (or HTML-dataset-driven map) of scenes with their own init/render functions keeps the main render loop reusable across any number of diagrams.
  4. Per-scene interactive controls (like TrackballControls) just need their own camera/DOM-element pairing, same as a full-page three.js app.

Connects To

  • TrackballControls: the interactive-camera addon used per scene in this pattern.