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

Pinned 3D Scenes vs Scroll Scrub: Which One You Actually Need

September 8, 2026 · AETumi

**Key answer: Pinning and scrubbing are two independent decisions that people confuse because they usually appear together. Pinning fixes a section in the viewport so it stays on screen while the page keeps scrolling underneath — it answers "does the scene hold still?" Scrubbing ties an animation's progress directly to the scrollbar so it plays forward and rewinds as you scroll — it answers "does scroll drive the timeline?" The pinned-3d-scenes-vs-scroll-scrub question is therefore a false either/or: the strongest hero teardowns pin and scrub, a background parallax scrubs without pinning, a modal takeover pins without scrubbing, and most content further down the page should do neither and simply reveal once on entry. Choose by the job: pin when the scene must own the viewport for its whole story, scrub when the user should control that story's timeline, and reveal when the moment is a one-shot accent. Getting this pairing right is what separates a guided experience from a scroll that feels hijacked.**

Table of contents

What pinning and scrubbing each mean

The whole pinned-3d-scenes-vs-scroll-scrub debate clears up the moment you separate the two words. Pinning is a layout behavior: a section is held fixed in the viewport — visually it stops scrolling while the surrounding document continues — for a defined stretch of scroll distance, then releases. Scrubbing is a timing behavior: an animation's playhead is bound to scroll position, so 40% through the trigger range means 40% through the animation, and scrolling back rewinds it.

The AETumi system at a glanceAETumi technical diagram — The AETumi system at a glanceScroll progressPin / scrubGSAPThree.jsRevealFallbackScroll3D
The AETumi system at a glance
Ring Galaxy
Ring Galaxy — live preview from the AETumi library

They are orthogonal. You can pin without scrubbing (a section sticks while a self-contained animation plays on a timer), scrub without pinning (a background scene animates against page scroll while the section flows normally), do both (the classic pinned hero teardown that plays out over two screen-heights), or do neither (a reveal that fires once). Confusing the two is what leads people to pin sections that should just scrub, or to scrub scenes that needed pinning to make sense.

What you'll learn

  • Precisely what pinning and scrubbing each control, and why they are independent
  • How to implement a pinned scene and a scrubbed animation separately in Three.js
  • Why the best hero moments combine both, and how to wire that cleanly
  • When a one-shot reveal beats either technique
  • The React Three Fiber equivalent with Drei's ScrollControls
  • The performance, SEO, and accessibility cost of each choice
  • A decision matrix mapping content types to pin, scrub, both, or reveal

Why the distinction matters

Choosing pin, scrub, both, or reveal is the single most consequential design decision in a scroll-driven page, and it is one people get wrong by defaulting. Pin everything and the page feels like a hijacked scroll — the visitor pushes the wheel and nothing moves except a slideshow they did not ask for. Scrub a heavy scene that is not pinned and it drifts past before its story lands. Reveal a moment that needed pinning and the pitch evaporates in half a second.

Hourglass Galaxy
Hourglass Galaxy — live preview from the AETumi library

The distinction also has real engineering consequences. Pinning changes document layout and must survive resize and refresh; scrubbing is a progress-mapping problem that lives entirely in your render loop. Treating them as one thing means you cannot reason about either bug cleanly. This article pairs with GSAP Three.js scroll, which covers wiring ScrollTrigger to a scene in depth; here the focus is the decision itself.

The process, step by stepAETumi technical diagram — The process, step by stepTrack scrollprogressMap to animationPin or scrubThrottleoffscreenReduced-motionfallback
The process, step by step

Architecture: two independent axes

Model your scroll page as two switches per section: pinned? (yes/no) and scrubbed? (yes/no). That gives four cells, and every scroll pattern you have admired lives in one of them. A pinned, scrubbed section is a hero teardown. A non-pinned, scrubbed section is a parallax or background drift. A pinned, non-scrubbed section is a timed takeover. A non-pinned, non-scrubbed section is a reveal.

Forma
Forma — live preview from the AETumi library

Underneath, the render architecture is identical in every cell: an input layer produces a normalized progress value, a small state object holds it, and a single requestAnimationFrame loop eases toward it and draws. Pinning and scrubbing only change how progress is produced — pinning defines the scroll range progress is measured against, scrubbing decides whether progress maps to the timeline at all. Keep that render core constant and the four patterns become configuration, not four different codebases.

Pinning a scene

Pinning holds a section on screen. GSAP's ScrollTrigger handles the layout mechanics — reserving space so the document does not jump — better than hand-rolled position: sticky for anything non-trivial:

Prism Streaks
Prism Streaks — live preview from the AETumi library
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);

ScrollTrigger.create({
  trigger: '#scene',
  start: 'top top',
  end: '+=200%',   // stay pinned for two viewport heights of scroll
  pin: true,
  scrub: false     // pinned, but the animation is NOT tied to scroll yet
});

Explanation: pin: true fixes #scene in the viewport from when its top hits the viewport top until 200% (two screen-heights) of scroll have passed, then releases it; scrub: false means the scene holds still — pinned but not yet driven by scroll. Expected behavior: the section sticks while the page scrolls two screens' worth beneath it. Trade-off: pinning inserts a spacer into layout, so resize and refresh must be handled (ScrollTrigger does this on refresh); a bad end value leaves an awkward gap. position: sticky is lighter for a simple stick with no reserved-range choreography.

Scrubbing an animation

Scrubbing binds the animation playhead to the scrollbar. Add scrub and let ScrollTrigger write its 0-to-1 progress into your Three.js state — never let it touch the scene directly:

const state = { progress: 0 };

ScrollTrigger.create({
  trigger: '#scene',
  start: 'top bottom',   // begins as the section enters from the bottom
  end: 'bottom top',
  scrub: 1,              // 1s catch-up smoothing; bound to scroll, not a timer
  onUpdate: (self) => { state.progress = self.progress; }
});

Explanation: scrub: 1 ties progress to scroll with one second of smoothing, and onUpdate writes self.progress into shared state that the render loop reads. There is no pin here — the section flows normally while its scene animates against scroll, the recipe for background parallax. Expected behavior: scrolling forward advances the scene, scrolling back rewinds it, with a smooth catch-up rather than instant snapping. Trade-off: without pinning, a scene with a long story can scroll past before it finishes; if the animation needs the viewport to itself for its full arc, you also need pinning.

Pin + scrub together

The signature hero moment — a product rotating and exploding into its parts as you scroll — is both pinned and scrubbed. The scene holds the viewport while its timeline is bound to scroll:

const state = { progress: 0 };

ScrollTrigger.create({
  trigger: '#hero',
  start: 'top top',
  end: '+=200%',
  pin: true,        // hold the scene on screen…
  scrub: 1,         // …and drive its timeline from the scrollbar
  onUpdate: (self) => { state.progress = self.progress; }
});

// single render loop, decoupled from scroll
let current = 0;
function tick() {
  requestAnimationFrame(tick);
  current += (state.progress - current) * 0.08;
  mesh.rotation.y = current * Math.PI * 2;
  camera.position.z = 6 - current * 3;
  renderer.render(scene, camera);
}
requestAnimationFrame(tick);

Explanation: pin: true plus scrub: 1 gives the scene two screen-heights of dedicated, scroll-driven runtime; ScrollTrigger only writes state.progress, and the rAF loop is the single place that eases and renders. Expected behavior: the hero locks to the viewport and the teardown plays forward and backward under the visitor's control. Trade-off: this is the most expensive pattern — a persistent render budget for the whole pinned range — so reserve it for the one or two moments that carry the pitch, not every section.

Reveal: neither pin nor scrub

Most content below the hero should neither pin nor scrub. A reveal fires a self-contained animation once when an element enters view, then leaves it alone — lighter, calmer, and correct for supporting sections:

ScrollTrigger.create({
  trigger: '#feature',
  start: 'top 80%',        // when the section is 80% up the viewport
  once: true,              // fire a single time, no scrub, no pin
  onEnter: () => gsap.to(model.rotation, { y: Math.PI / 6, duration: 1, ease: 'power2.out' })
});

Explanation: once: true with onEnter triggers a one-shot timeline; there is no pinning and no scrub binding, so the animation plays on its own easing and then stops. Expected behavior: the model settles into place as the section arrives and stays there as the user continues. Trade-off: you lose reversibility and scroll control, which is exactly the point — a reveal is an accent, not a takeover, and using it for supporting content keeps the pinned/scrubbed hero feeling special.

React Three Fiber equivalent

In React or Next.js, Drei's ScrollControls gives you the same axes declaratively. pages defines the scroll range (the pinning-equivalent runway), and useScroll().offset is the scrubbed progress:

import { ScrollControls, useScroll } from '@react-three/drei';
import { useFrame } from '@react-three/fiber';
import { useRef } from 'react';

function Hero() {
  const ref = useRef();
  const scroll = useScroll();
  useFrame(() => {
    const p = scroll.offset;                       // scrubbed 0 → 1 progress
    ref.current.rotation.y += (p * Math.PI * 2 - ref.current.rotation.y) * 0.08;
  });
  return <mesh ref={ref}>{/* geometry + material */}</mesh>;
}
// <ScrollControls pages={3}><Hero /></ScrollControls>  → canvas is effectively pinned; scroll scrubs it

Explanation: <ScrollControls pages={3}> fixes the canvas and creates three pages of scroll runway (the pin), while useScroll().offset provides the scrubbed progress that useFrame maps onto the scene. Expected behavior: the canvas stays put and the model is driven by scroll across three pages. Trade-off: R3F's ScrollControls couples pinning and scrubbing more tightly than vanilla ScrollTrigger, which is convenient for hero scenes but less flexible if you want a non-pinned scrub; see React Three Fiber for when to choose it over raw Three.js.

Real product evidence

The demo below is a production hero from the AETumi library that uses the pin + scrub pattern — the scene locks to the viewport and its teardown timeline is bound to the scrollbar. Watch two things: the subject holds the center of the frame the whole time (pinning), and the motion reverses cleanly when you scroll back up (scrubbing). Seeing both behaviors in one scene is the clearest way to internalize that they are separate decisions cooperating, not a single effect. It proves the practical claim of this guide — that the memorable hero moment is specifically the pinned-and-scrubbed cell of the matrix — and it ships as editable source you own for life.

Performance

The four cells cost differently. Pinned + scrubbed is the heaviest because it holds a live render budget for its whole pinned range; a non-pinned scrub is lighter since the scene leaves the viewport quickly; a reveal is cheapest because it renders a short one-shot animation and can then stop entirely. Whichever you choose, apply the same essentials: cap the pixel ratio with renderer.setPixelRatio(Math.min(devicePixelRatio, 2)), keep the scroll input decoupled from a single requestAnimationFrame render loop, and pause the loop with an IntersectionObserver when the canvas is offscreen. For reveals, stop the loop after the one-shot animation settles rather than rendering forever. The rule of thumb: the more of the viewport-time a pattern occupies, the tighter its frame budget must be.

SEO

Pinning has a specific SEO wrinkle: because a pinned section reserves extra scroll height, a page with several pinned scenes can become very tall, which is fine as long as the crawlable content is not trapped inside the canvas. The universal rule holds regardless of pin or scrub — all rankable text, links, and CTAs live in real HTML outside the <canvas>, and the 3D is an enhancement on top. Lazy-mount the scene after first paint so neither pinning nor scrubbing blocks Largest Contentful Paint. A pinned, scrubbed hero indexes exactly like a flat page when the DOM carries the content and the WebGL layer carries only the experience.

Accessibility

Pinning and scrubbing both raise motion-sensitivity concerns, and pinning additionally risks the sense that scrolling is "stuck." Honor prefers-reduced-motion by disabling the pin and scrub for those users and showing a single static frame in normal document flow:

const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduce) {
  renderer.render(scene, camera);   // static frame, no pin, no scrub, no loop
}

Beyond that: never hijack the wheel (scrubbing should follow the scrollbar, not fight it), keep all content and controls in the DOM so keyboards and screen readers work, and give any control layered over a pinned scene a visible focus state. A reduced-motion visitor should get the whole message with the section flowing normally and the scene frozen.

Production trade-offs

Pinning adds layout complexity — reserved space, resize handling, refresh on route change — and can feel claustrophobic if overused. Scrubbing adds a persistent render budget and a dependency (ScrollTrigger is ~40KB gzipped) and demands the decoupled loop to avoid jank. Combining them multiplies both costs, which is why the honest default is to spend the pin + scrub budget on one hero and reveal the rest. And the most honest trade-off of all: if you cannot name what the third dimension or the reversibility communicates, neither pinning nor scrubbing is the answer — a static image or a short muted video will land the idea for a fraction of the engineering.

When to pin, scrub, or reveal

GoalChoose
A hero must own the viewport for its whole storyPin + scrub
The user should control a timeline (teardown, walkthrough)Scrub (pin if it needs the full viewport)
A background scene should drift with the pageScrub, no pin
A section must hold still while timed content playsPin, no scrub
A supporting element should animate in onceReveal (neither)

When NOT to pin or scrub

Avoid pin/scrub when…Use instead
Every section is pinned "for impact"Pin one hero, reveal the rest
The content is text-first (docs, blog, B2B)Flat HTML, maybe a CSS reveal
A single clip conveys the whole ideaAn autoplay-muted, lazy-loaded video
The audience is on constrained devicesA static hero image with a poster
Nothing about the scene needs reversibilityA one-shot reveal or no animation

Decision matrix

PatternPinned?Scrubbed?CostBest for
Hero teardownYesYesHighThe one moment that carries the pitch
Background parallaxNoYesMediumAmbient depth behind flowing content
Timed takeoverYesNoMediumA held section with autoplay content
RevealNoNoLowSupporting sections, one-shot accents
Static frameNoNoVery lowReduced-motion, text-first, low-end devices

How AETumi approaches it

Expert Note — decide pin and scrub separately, then combine. The cleanest scroll builds we ship treat pinning and scrubbing as two config flags on a shared render core, not as one bundled effect. When a hero misbehaves, ask which flag is wrong: if the scene drifts off before its story ends, it needed pinning; if it feels like a slideshow the user cannot steer, it needed scrubbing or should have been a reveal. Naming the axis fixes the bug faster than rewriting the scene.

Expert Note — reserve pin + scrub for one moment per page. Pinning is a limited budget of the visitor's patience as much as of GPU frames. We default to a single pinned, scrubbed hero and reveal everything below it; the hero reads as special precisely because the rest of the page scrolls normally. A page where every section pins is a page that feels broken, no matter how good each individual scene looks.

AETumi is an AI-native 3D web platform, and its scroll templates expose exactly this split — pinning and scrubbing as deliberate choices layered over a single, performance-tuned render loop. Each template ships with full source, a capped pixel ratio, offscreen pausing, and a reduced-motion path, plus an AI build prompt you can hand to Claude Code, Cursor, or the AETumi MCP to restyle a hero or convert a pinned takeover into a reveal without touching the render core. You buy once and own it for life — Standard $19, Pro $39, Premium $99, and Full Stack $129 (full source plus the AETumi MCP workflow) — so the pin/scrub plumbing is solved and you spend your time choosing the right pattern for each section.

GitHub and technical proof

The threejs-scroll-animation repository in the AETumi GitHub organization is a runnable reference for the patterns above. It uses Three.js r160 as native ES modules over an import map (no UMD bundle), so the source is readable with no build step, and it demonstrates a ScrollTrigger instance configured for pin + scrub that writes only to a state object, a single requestAnimationFrame loop that eases and renders, a capped pixel ratio, and a prefers-reduced-motion branch. Its limitations are honest: it is a single-scene demonstrator rather than a full multi-section site, so it shows the pinned-and-scrubbed cell in isolation rather than orchestrating a whole page of mixed patterns, and it assumes a modern WebGL-capable browser. Use it to feel the difference between pinning and scrubbing directly, then compose the other three cells around it.

How the pieces connectAETumi technical diagram — How the pieces connectScrollProgressMapSceneCameraFrame
How the pieces connect

FAQ

What is the difference between pinning and scrubbing? Pinning fixes a section in the viewport so it stays on screen while the page scrolls beneath it for a defined range, then releases. Scrubbing binds an animation's progress to the scrollbar so scrolling forward advances it and scrolling back rewinds it. They are independent: you can do either alone, both together, or neither. Confusing them is the most common cause of scroll pages that feel hijacked or that scroll past their own story.

Do I always need to pin a scrubbed scene? No. Pin only when the scene needs the whole viewport for its full arc — a hero teardown that plays over two screen-heights. A background parallax or ambient drift scrubs happily without pinning because it is meant to pass by. Pin when the story would be cut off otherwise; skip pinning when the scene is meant to flow with the page.

Is position: sticky the same as pinning? For a simple "stick to the top" with no reserved scroll runway, position: sticky is lighter and sufficient. GSAP ScrollTrigger's pin does more: it reserves layout space for a defined scroll range, handles resize and refresh, and coordinates with scrub progress. Use sticky for trivial cases and ScrollTrigger's pin when the pinned section has a scroll-driven timeline.

When should I use a reveal instead of pin or scrub? Use a reveal for supporting content below the hero — anything that benefits from a one-shot entrance animation but does not need to own the viewport or be reversible. Reveals are lighter, calmer, and keep your pinned, scrubbed hero feeling special by contrast. Reserve pin + scrub for the one or two moments that carry the pitch.

How do I keep a pinned, scrubbed hero performant? Cap the pixel ratio at 2, keep the scroll input decoupled from a single requestAnimationFrame render loop, and pause with an IntersectionObserver when the canvas leaves the viewport. Pin + scrub is the heaviest pattern because it holds a live render budget for its whole range, so test it on a mid-range phone and simplify geometry before shipping if it cannot hold a steady frame rate.

Conclusion

Pinned 3D scenes vs scroll scrub is not a choice between two things — it is two independent switches you set per section. Pinning decides whether the scene holds the viewport; scrubbing decides whether scroll drives its timeline. Pin and scrub together for the one hero that carries the pitch, scrub alone for ambient depth, pin alone for a timed takeover, and reveal everything else. Keep the render core constant, cap the pixel ratio, decouple scroll from a single loop, and ship a reduced-motion frame, and each pattern becomes configuration rather than a rewrite. Browse the AETumi 3D scroll collection for templates that combine all four cells cleanly, study the working code in threejs-scroll-animation, and go deeper on the wiring in GSAP Three.js scroll. AETumi is an AI-native 3D web platform — its scroll templates ship with full source and AI build prompts, buy once own for life. Start with a template at aetumi.app/pricing and pick the right pattern for every section.

More from the AETumi library

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

Browse all 3D scroll templates →