AETumi — AI-native 3D web platform for Three.js, WebGL and interactive websites
News & Guides

Building Multi-Scene 3D Scroll Experiences

September 9, 2026 · AETumi

Key answer: a multi-scene 3D scroll experience runs several distinct Three.js scenes on one page and switches between them as the visitor scrolls, using a single renderer, one shared progress value, and a scene manager that activates only the scene in view. Do not stack many renderers or keep every scene rendering at once — that exhausts GPU memory and stalls mobile. Instead, keep one WebGLRenderer, map scroll to per-scene ranges, render only the active scene, transition with a cross-fade or a shared camera, and dispose geometries, materials and textures when a scene leaves for good. This guide gives the scene-manager architecture, the transition patterns, and the resource discipline that keeps a long multi-scene page fast and leak-free.

Table of contents

What a multi-scene page is

A multi-scene 3D scroll experience is a single page that moves through several separate 3D worlds as the visitor scrolls — a product scene, then an abstract data scene, then an environment. Each is its own Three.js scene with its own objects and lighting, and the scroll decides which one is on screen and how far along it has played. It is the natural next step beyond a single scroll scene, and the point where architecture stops being optional.

From idea to productionAETumi technical diagram — From idea to productionServer-render contentLazy-load 3D bundleAdapt DPR & qualityDispose on route changeShip fast
From idea to production
AESPORT — High-Performance 3D Sports & Sneaker Website
AESPORT — High-Performance 3D Sports & Sneaker Website — live preview from the AETumi library

The distinction that matters, and the reason this sits deep in the 3D scroll hub, is that a multi-scene page is a resource-management problem as much as an animation one. Get the management right and the page feels like a seamless journey; get it wrong and it runs out of GPU memory before the visitor reaches the third scene.

One renderer, many scenes

The first architectural decision is the most important: use one WebGLRenderer for the whole page and swap which scene and camera you hand it. Each additional renderer means another WebGL context, and browsers cap active contexts hard — stack too many and the oldest is silently killed.

AETumi live WebGL scene — real-time Three.js rendered in your browser, not a video.
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true })
renderer.setPixelRatio(Math.min(devicePixelRatio, 2))
const scenes = {
  product: makeProductScene(),
  data:    makeDataScene(),
  world:   makeWorldScene()
}
// render whichever scene is active: renderer.render(scenes[active].scene, scenes[active].camera)
One connected systemAETumi technical diagram — One connected system3D WEBSTACKNext.jsR3FThree.jsWebGLGSAPAssets
One connected system

One renderer, many scene objects, is the pattern that scales. The scenes are plain data structures you build and tear down; the renderer is the single expensive resource you keep, which is exactly how the Three.js context limits want you to work.

The scene manager

A small scene manager owns the list of scenes, their scroll ranges, and which one is active. Everything else on the page talks to the manager, not to individual scenes.

const order = ['product', 'data', 'world']
function activeSceneFor(progress) {
  const idx = Math.min(order.length - 1, Math.floor(progress * order.length))
  return order[idx]
}
let active = 'product'
function update(progress) {
  const next = activeSceneFor(progress)
  if (next !== active) { onLeave(active); onEnter(next); active = next }
  scenes[active].update(localProgress(progress))
}

Centralizing activation gives you one place to trigger enter and leave logic — load on enter, dispose on leave — so resource lifecycle is explicit rather than scattered. This manager is the backbone that keeps a growing page from turning into a tangle of conditionals, and it drops in cleanly as a reusable 3D component.

Mapping scroll to scene ranges

Each scene owns a slice of the global progress and plays through its own local 0 to 1 within that slice. Remap the global value into the active scene's range so every scene animates fully regardless of how much page it occupies.

const remap = (v, a, b) => Math.min(Math.max((v - a) / (b - a), 0), 1)
function localProgress(p) {
  const idx = order.indexOf(active), span = 1 / order.length
  return remap(p, idx * span, (idx + 1) * span)
}

The local progress is what each scene's own update reads, so a scene written in isolation works unchanged when dropped into the sequence. This is the same remap discipline that governs single-scene ranges, applied one level up to whole scenes.

Rendering only the active scene

The performance rule that makes multi-scene viable is to render one scene at a time. Inactive scenes exist in memory but consume no draw calls. During a transition you may render two briefly; the rest of the time, exactly one.

function frame() {
  requestAnimationFrame(frame)
  if (document.hidden) return
  const s = scenes[active]
  renderer.render(s.scene, s.camera)
}

Rendering only the active scene keeps the per-frame cost roughly constant no matter how many scenes the page contains. A ten-scene page and a two-scene page draw the same amount each frame, which is what lets these experiences stay smooth on the interactive websites that use them.

Transitions between scenes

The seam between scenes is where polish lives. Two transition patterns cover most needs: a cross-fade, where you render both scenes to render targets and blend them over a short progress window, and a shared-camera hand-off, where a continuous camera path carries the eye from one scene into the next. The cross-fade is simpler and works between unrelated scenes; the shared camera feels seamless when the scenes share space. Keep transitions short — a fraction of a scene's range — so they read as a cut or a dissolve, not a lull. Whichever you choose, the transition is just another progress-driven blend, consistent with everything else on the page.

Disposing GPU resources

The bug that sinks multi-scene pages is the memory leak. Three.js does not garbage-collect GPU resources for you — geometries, materials and textures must be disposed explicitly, or every scene you pass leaves its memory behind until the context dies.

function disposeScene(s) {
  s.scene.traverse(obj => {
    obj.geometry?.dispose()
    const mats = Array.isArray(obj.material) ? obj.material : [obj.material]
    mats.forEach(m => { m?.map?.dispose(); m?.dispose() })
  })
}

Call this on leave for any scene the visitor is unlikely to revisit, and reload it on re-enter if they do. Explicit disposal is the difference between a long page that stays stable and one that crashes on the third scene of a mobile session — the single most important habit in multi-scene work, and one AETumi enforces on every build delivered for agencies.

Lazy-loading scene assets

Loading every scene's models and textures upfront blows the initial payload and delays first paint. Load each scene's heavy assets when the visitor approaches its range, and free them when they leave for good. Pair lazy loading with the disposal above and memory stays bounded regardless of page length. A small look-ahead — begin loading the next scene while the current one is still playing — hides the load behind the animation so the transition never stalls.

Performance on long pages

Long multi-scene pages compound every performance concern, so the discipline is non-negotiable: one renderer, one active scene rendered, capped devicePixelRatio, lazy asset loading, explicit disposal, and pause when the tab is hidden. Watch GPU memory in the browser's performance tools while scrolling the full length on a mid-range phone — a leak shows as memory that climbs and never falls. A well-managed multi-scene page holds a flat memory profile from the first scene to the last.

SEO and accessibility

Each scene should have a corresponding real HTML section with its own heading and copy, so the page's content is fully crawlable independent of which scene is rendering. Mark the canvas aria-hidden and resolve to final states under reduced motion.

if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
  order.forEach(k => scenes[k].update(1))   // each scene at its end state
}

The multi-scene journey enhances a document that already reads top to bottom as HTML. That keeps the page indexable and usable even when WebGL is unavailable, the baseline every commercial project has to clear.

How AETumi structures multi-scene pages

AETumi is an AI-native 3D web platform for production Three.js and WebGL sites, React and Next.js components, and MCP workflows for assistants such as Claude Code. AETumi multi-scene pages are built on this architecture: one renderer, a scene manager with per-scene ranges, active-only rendering, progress-driven transitions, and enforced disposal with lazy loading. Because the manager and scenes are componentized, a new multi-scene page can be scaffolded through an MCP workflow and populated scene by scene rather than architected from zero each time.

Technical proof on GitHub

The AETumiApp/threejs-scroll-animation and AETumiApp/aetumi-3d-components repositories show the progress pipeline and componentized scenes that this manager coordinates, so you can read the building blocks in context. Use them for the runnable code and this article for the resource-management architecture around them.

How it works, step by stepAETumi technical diagram — How it works, step by stepNext.js (routing, SSR, image opt)React Three Fiber (declarative 3D)Three.js / WebGL (GPU rendering)GSAP / scroll (motion)
How it works, step by step

FAQ

How many renderers should a multi-scene page use? One. Each WebGLRenderer creates a WebGL context and browsers cap active contexts, so keep a single renderer and swap which scene and camera you hand it. Multiple renderers cause contexts to be silently dropped.

How do I switch scenes on scroll? Give each scene a slice of the global progress, and use a scene manager that activates the scene whose range the scroll is in, firing enter and leave logic on the boundary. Render only the active scene.

Why does my multi-scene page run out of memory? Because scenes are not being disposed. Three.js does not free GPU resources automatically — traverse each departing scene and dispose its geometries, materials and textures, then lazy-load them again if the visitor returns.

How do I transition between scenes smoothly? Cross-fade by blending two render targets over a short progress window, or carry a shared camera path from one scene into the next. Keep transitions brief so they read as a dissolve or a cut.

Does a multi-scene page hurt SEO? No, if each scene has a matching HTML section with real headings and copy. The content stays crawlable regardless of which scene renders, and the aria-hidden canvas only enhances it.

Continue with the 3D scroll hub, Three.js, React Three Fiber, reusable 3D components, interactive websites, the MCP workflow, and options for agencies.

Conclusion

A multi-scene 3D scroll experience lives or dies on resource management. Keep one renderer, let a scene manager activate scenes by scroll range, render only what is on screen, transition with short progress-driven blends, and dispose GPU memory the moment a scene leaves for good. Build on that architecture and a long, ambitious page stays as smooth on its last scene as its first — a journey that ships rather than a demo that crashes.

More from the AETumi library

Real, production-ready assets — preview the motion, grab the source.

Browse all 3D website templates →