Three.js scenes in SvelteKit
Three.js needs the DOM, so initialize it only on the client. Keep the renderer scoped to a single container, update the camera on resize, and dispose of GPU resources when the route unmounts.
Best practices
- Create the renderer and scene inside
onMountto avoid SSR mismatch errors. - Cap
devicePixelRatioto keep GPU load steady on high-DPI screens. - Use
ResizeObserverfor responsive resizing instead of window resize only. - Call
dispose()on geometry and materials, thenrenderer.dispose(). - Remove the canvas node from the DOM when you destroy the scene.
- Pause animation loops when the page is hidden to save cycles.
SvelteKit-friendly setup
<script lang="ts">
import { onDestroy, onMount } from "svelte";
import * as THREE from "three";
let containerEl;
let renderer;
let camera;
let scene;
let mesh;
let resizeObserver;
let frame = 0;
const resize = () => {
const { width, height } = containerEl.getBoundingClientRect();
renderer.setSize(width, height, false);
camera.aspect = width / height;
camera.updateProjectionMatrix();
};
const render = (time) => {
mesh.rotation.y = time * 0.0006;
renderer.render(scene, camera);
frame = requestAnimationFrame(render);
};
onMount(() => {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(2, 1.5, 3);
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
containerEl.appendChild(renderer.domElement);
mesh = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0x38b884 })
);
scene.add(mesh);
scene.add(new THREE.AmbientLight(0xffffff, 0.7));
resize();
resizeObserver = new ResizeObserver(resize);
resizeObserver.observe(containerEl);
frame = requestAnimationFrame(render);
});
onDestroy(() => {
cancelAnimationFrame(frame);
resizeObserver?.disconnect();
mesh?.geometry.dispose();
mesh?.material.dispose();
renderer?.dispose();
});
</script> Checklist
- Scene, camera, and renderer live inside the Svelte component.
renderer.setSize()uses the container width and height.- Cleanup detaches DOM and disposes GPU resources.