Three.js - Responsive Design
Three.js - Responsive Design
Section titled “Three.js - Responsive Design”A modern web experience must be responsive, meaning it should look and function well on any screen size, from a large desktop monitor to a small smartphone. A static Three.js scene will appear cropped or stretched on different devices. This chapter covers the essential techniques to make your 3D scenes fully responsive.
The Core Problem: A Fixed-Size Canvas
Section titled “The Core Problem: A Fixed-Size Canvas”By default, the <canvas> element where Three.js renders your scene has a fixed size. When the browser window is resized, two things need to be updated:
- The Renderer: It needs to know the new dimensions to draw to.
- The Camera: A perspective camera’s
aspectratio must match the new viewport dimensions to prevent the scene from looking squashed or stretched.
Solution: The Resize Handler
Section titled “Solution: The Resize Handler”The solution is to listen for the browser’s resize event and update the necessary components within a handler function. Let’s set up a modern project structure first.
1. Project Setup (Modern Workflow)
Section titled “1. Project Setup (Modern Workflow)”We’ll use a standard setup with separate HTML, CSS, and JavaScript files. It is highly recommended to use a package manager like npm and a build tool like Vite for managing dependencies and development.
# 1. Create a project folder and navigate into itmkdir my-threejs-appcd my-threejs-app
# 2. Initialize a node project and install dependenciesnpm init -ynpm install three lil-guinpm install --save-dev vite
# 3. Create your filestouch index.html style.css main.js2. HTML (index.html)
Section titled “2. HTML (index.html)”The HTML file is minimal. It provides a container for our canvas and loads our main script as a module.
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Responsive Three.js Scene</title> <link rel="stylesheet" href="./style.css"></head><body> <canvas class="webgl"></canvas> <script type="module" src="./main.js"></script></body></html>3. CSS (style.css)
Section titled “3. CSS (style.css)”The CSS ensures our canvas fills the entire viewport.
* { margin: 0; padding: 0;}
html, body { overflow: hidden; /* Prevent scrollbars */}
.webgl { position: fixed; top: 0; left: 0; outline: none;}4. JavaScript (main.js)
Section titled “4. JavaScript (main.js)”This is where the magic happens. We set up our scene and create the resize handler.
import * as THREE from 'three';import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// Sizesconst sizes = { width: window.innerWidth, height: window.innerHeight}
// Sceneconst scene = new THREE.Scene();
// Objectconst mesh = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1, 5, 5, 5), new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true }));scene.add(mesh);
// Cameraconst camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100);camera.position.z = 3;scene.add(camera);
// Rendererconst canvas = document.querySelector('canvas.webgl');const renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true });renderer.setSize(sizes.width, sizes.height);renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// --- RESIZE HANDLER ---window.addEventListener('resize', () => { // Update sizes sizes.width = window.innerWidth; sizes.height = window.innerHeight;
// Update camera camera.aspect = sizes.width / sizes.height; camera.updateProjectionMatrix();
// Update renderer renderer.setSize(sizes.width, sizes.height); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));});
// Animation Loopconst tick = () => { mesh.rotation.y += 0.01; renderer.render(scene, camera); window.requestAnimationFrame(tick);}
tick();Handling Pixel Density and Anti-Aliasing
Section titled “Handling Pixel Density and Anti-Aliasing”Pixel Ratio for Sharpness
Section titled “Pixel Ratio for Sharpness”High-density screens (like Apple’s Retina displays) have more physical pixels than logical pixels. To get a crisp, sharp render on these screens, you must set the renderer’s pixel ratio. window.devicePixelRatio provides this value. We cap it at 2 with Math.min() because higher values offer diminishing visual returns while heavily impacting performance.
// Set pixel ratio for sharp rendering on high-DPI screensrenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));Anti-Aliasing for Smooth Edges
Section titled “Anti-Aliasing for Smooth Edges”Aliasing causes the edges of 3D objects to look jagged or “stair-stepped”. We can smooth these edges by enabling anti-aliasing when creating the renderer. Note that this comes with a performance cost, so use it judiciously.
// Enable anti-aliasing when instantiating the rendererconst renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true});Common Pitfalls
Section titled “Common Pitfalls”- Forgetting
camera.updateProjectionMatrix(): After changing the camera’saspectratio (or any other fundamental property likefov), you must call this method to apply the changes. - Performance: The
resizeevent can fire very rapidly. For complex resize logic, you might consider “debouncing” the event handler to prevent it from running too frequently and causing performance issues. - Accessibility: A
<canvas>element is not accessible to screen readers by default. Provide a fallback content for better accessibility:<canvas>A 3D visualization of a rotating cube.</canvas>