WebGLRenderer and Resize

Configuring WebGLRenderer and Handling Resize

new THREE.WebGLRenderer(options) creates a canvas with a WebGL 2 context; the options are Context Attributes's context attributes (antialias is off by default). setPixelRatio(Math.min(devicePixelRatio, 2)) keeps edges sharp without shading nine times the pixels on a 3x phone, and setSize() sizes the canvas. The chapter's createStage({ width, height }) in bookshelf.js does this and adds a background, a camera and two lights.

On resize, set camera.aspect, call camera.updateProjectionMatrix() and renderer.setSize(). For a canvas in a flexible layout, use a ResizeObserver and setSize(width, height, false), which leaves its CSS size alone. renderer.info counts the last frame's work: 7 draw calls and 84 triangles for the shelf.

A WebGLRenderer that follows its container with a ResizeObserver and reports renderer.infoHTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f5f1e8; font: 13px system-ui, sans-serif; color: #263238; }
  /* drag the corner of the box to resize it */
  #box { width: 580px; max-width: calc(100% - 16px); height: 260px; margin: 8px; resize: both; overflow: hidden;
         border: 1px dashed #a89f8c; min-width: 150px; min-height: 100px; position: relative; }
  #box canvas { width: 100%; height: 100%; display: block; }
  #info { position: absolute; left: 6px; top: 6px; background: rgb(255 255 255 / 0.85); padding: 4px 6px; font: 12px monospace; }
</style>
<script type="importmap">
{ "imports": {
  "three": "https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js",
  "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/" } }
</script>
<div id="box"><div id="info"></div></div>
<p style="margin:0 8px">Drag the box's bottom-right corner: the camera aspect and canvas size follow.</p>
<script type="module">
import * as THREE from 'three';

const box = document.getElementById('box');
// Context attributes go in the constructor: antialias is off by default
const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: 'high-performance' });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));   // sharp, but never more than 2×
box.prepend(renderer.domElement);

const scene = new THREE.Scene();
scene.background = new THREE.Color('#fbf8f2');
const camera = new THREE.PerspectiveCamera(35, 2, 0.1, 50);
camera.position.set(0, 0.4, 4);
camera.lookAt(0, -0.1, 0);
scene.add(new THREE.HemisphereLight('#ffffff', '#8d6e63', 1.2));
const sun = new THREE.DirectionalLight('#ffffff', 2.5);
sun.position.set(2, 3, 4);
scene.add(sun);
const plank = new THREE.Mesh(new THREE.BoxGeometry(3.2, 0.06, 0.4), new THREE.MeshStandardMaterial({ color: '#8d6e63' }));
plank.position.y = -0.52;
scene.add(plank);
['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f'].forEach((color, i) => {
  const book = new THREE.Mesh(new THREE.BoxGeometry(0.42, 0.98, 0.12), new THREE.MeshStandardMaterial({ color }));
  book.position.x = (i - 2.5) * 0.5;
  scene.add(book);
});

function render() {
  renderer.render(scene, camera);
  const { calls, triangles } = renderer.info.render;
  const size = renderer.getDrawingBufferSize(new THREE.Vector2());
  document.getElementById('info').textContent =
    `CSS ${box.clientWidth}×${box.clientHeight}, buffer ${size.x}×${size.y}\n` +
    `aspect ${camera.aspect.toFixed(2)} · ${calls} draw calls · ${triangles} triangles`;
}

// Follow the container: update the aspect, the projection and the drawing buffer
new ResizeObserver(() => {
  const w = box.clientWidth, h = box.clientHeight;
  camera.aspect = w / h;
  camera.updateProjectionMatrix();
  renderer.setSize(w, h, false);              // false: leave the canvas's CSS size to the stylesheet
  render();
}).observe(box);
</script>