Key answer: Scrollytelling websites are pages where a story unfolds as the reader scrolls — a graphic, scene, or map stays pinned while stepped text advances beside it, and each step drives a change in the visual. The reliable structure is a sticky graphic paired with a column of "steps"; as each step enters the viewport it fires a trigger that updates the pinned visual. You detect steps with the Intersection Observer API or a scroll library, keep the visual and the step-detection decoupled, and always ship a fallback where the story reads as plain stacked sections. Done well, scrollytelling turns a passive read into a guided sequence; done poorly, it fights the reader for control of the page. It is the narrative pattern behind data-journalism features, product walkthroughs, and cinematic landing pages.
Scrolling is the one interaction every visitor already knows, and scrollytelling websites turn that familiar gesture into a narrative device: the reader advances the story by doing the thing they were going to do anyway. This article explains what scrollytelling is, the architecture that keeps it smooth, how to wire the steps, and — just as importantly — when a static page tells the story better.
Table of contents
- What scrollytelling actually is
- What you'll learn
- Why scrollytelling matters
- Architecture: sticky graphic + stepped narrative
- The step-trigger model
- Text scrollytelling vs scene scrollytelling
- Technical implementation and code
- Real product evidence
- Performance
- SEO for scroll stories
- Accessibility
- Production trade-offs
- When to use scrollytelling
- When NOT to use it
- Decision matrix
- Expert notes
- How AETumi approaches it
- GitHub and technical proof
- FAQ
- Related resources
- Conclusion
What scrollytelling actually is
Scrollytelling — a blend of "scroll" and "storytelling" — is a page pattern where content is revealed and transformed in a deliberate sequence as the reader scrolls, rather than presented all at once. The signature layout is a sticky graphic (a chart, illustration, map, or 3D scene) that stays fixed in the viewport while a column of narrative steps scrolls past it. Each step corresponds to a state of the graphic: step one shows the whole map, step two zooms to a region, step three highlights a point. The reader controls the pace; the page controls the sequence.
The term rose to prominence in data journalism, but the pattern generalizes. A product page can pin a device and annotate each feature as you scroll; a landing page can pin a 3D scene and let the story advance through it. What unites them is a contract: one visual, many synchronized states, driven by scroll position. If you want the specifically 3D version of that contract, Three.js scroll animation covers driving a scene from scroll progress.
What you'll learn
- What defines a scrollytelling website versus an ordinary long page.
- The sticky-graphic-plus-steps architecture and why it stays smooth.
- How step triggers work with the Intersection Observer API.
- When plain text scrollytelling is enough and when a scene is worth it.
- Correct, framework-agnostic code for detecting steps and updating a visual.
- When scrollytelling helps the reader — and when it just hijacks the scrollbar.
Why scrollytelling matters
Scrollytelling matters because attention on the web is sequential and scarce, and most pages waste that by dumping everything into one scroll and hoping the reader assembles the order themselves. A scroll story does the ordering for them: it introduces one idea, lets it land, then advances. That pacing is why complex explanations — how a system works, how a product is assembled, how a dataset changes over time — land better as a guided sequence than as a wall of paragraphs and figures.
There is a second reason: engagement is measurable and honest here. Because the reader must scroll to progress, scroll depth maps directly to how much of the story they actually consumed. But the benefit is conditional. Scrollytelling helps only when the content is genuinely sequential; forced onto a page that is really just a list, it adds friction without adding meaning. The rest of this article treats that "when" as seriously as the "how."
Architecture: sticky graphic + stepped narrative
The dependable architecture has three parts. First, a sticky visual: a container pinned with CSS position: sticky (or a pinned section) so the graphic holds its place while content scrolls. Second, a steps column: a series of blocks, each tall enough to occupy a comfortable stretch of scrolling, that carry the narrative text. Third, a trigger layer: logic that detects which step is currently active and updates the sticky visual to that step's state.
Keeping these decoupled is what keeps the page smooth. The steps are ordinary DOM that scrolls at native speed; the trigger layer only reports which step is active; and the visual reacts to that report. Nothing in this chain blocks scrolling, because you never hijack the scroll itself — you observe it. That single principle, observe rather than intercept, is the difference between a story that glides and one that stutters or traps the reader.
The step-trigger model
The heart of a scrollytelling website is deciding when a step becomes "active." The modern, performant answer is the Intersection Observer API, which tells you when an element crosses a threshold in the viewport without you running code on every scroll event. You register each step, and when one enters a defined band — often the vertical center of the screen — you mark it active and update the visual.
The alternative, listening to the scroll event and measuring positions by hand, is both more code and more likely to jank, because it runs on the main thread every frame the user scrolls. Intersection Observer batches that work off the critical path. For stories that need finer control — a value that changes continuously across a step rather than snapping between discrete states — a scroll library like GSAP ScrollTrigger adds progress tracking on top; GSAP + Three.js scroll goes deep on that continuous case.
Text scrollytelling vs scene scrollytelling
Not every scroll story needs WebGL. It helps to separate two tiers. Text-and-image scrollytelling pins a static graphic — an SVG chart, a photo, a CSS illustration — and swaps or annotates it per step. It is cheap, robust, accessible by default, and correct for the large majority of narrative pages. Scene scrollytelling pins a live 3D scene and drives camera, model, or material state from the steps. It is more expensive and more fragile, and it earns its keep only when the subject is genuinely spatial or material — a product you rotate, a place you fly through, a process you see in three dimensions.
Choosing the lighter tier when it suffices is a mark of craft, not a compromise. A pinned SVG that updates crisply beats a heavy scene that stutters on a mid-range phone. Reach for the Three.js hub and WebGL hub only once the story truly needs depth; otherwise the static tier is the right default.
Technical implementation and code
Start with the layout. The sticky visual and the steps live side by side; CSS does the pinning with no JavaScript at all:
.scrolly { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; }
.scrolly__visual { position: sticky; top: 0; height: 100vh; }
.scrolly__step { min-height: 90vh; display: flex; align-items: center; }
Expected behavior: the visual pins to the top of the viewport and stays there while the step blocks — each nearly a screen tall — scroll past. Trade-off: position: sticky needs an ancestor tall enough to scroll within and no overflow: hidden on that ancestor, which is the single most common reason a sticky graphic "won't stick."
Next, detect the active step with Intersection Observer rather than a scroll handler:
const steps = document.querySelectorAll('.scrolly__step');
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
const index = Number(entry.target.dataset.step);
updateVisual(index); // react: swap image, move camera, highlight
}
}
}, { rootMargin: '-45% 0px -45% 0px', threshold: 0 });
steps.forEach((step) => observer.observe(step));
Expected behavior: a step is reported active when it crosses the middle band of the viewport (the negative top/bottom rootMargin shrinks the trigger zone to a horizontal line near center). Trade-off: this snaps between discrete states, which is perfect for stepped narratives but wrong when you need a continuous value across a step — for that, track progress instead.
For the continuous case, compute a 0-to-1 progress for a step from its bounding box, and let a render loop ease toward it rather than writing on every scroll tick:
let target = 0, current = 0;
function onScroll() {
const rect = stepEl.getBoundingClientRect();
const raw = 1 - rect.bottom / (window.innerHeight + rect.height);
target = Math.min(1, Math.max(0, raw));
}
addEventListener('scroll', onScroll, { passive: true });
function tick() {
current += (target - current) * 0.08; // ease toward target
applyProgress(current); // e.g. camera.position.z = 4 - current * 2
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
Expected behavior: applyProgress receives a smoothed value that trails the scroll slightly, which reads as cinematic rather than mechanical. Trade-off: the passive: true listener only reads scroll and stores a number — it never blocks the scroll — while the actual work happens in the decoupled loop; coupling them directly is the classic cause of scroll jank.
Finally, always provide a reduced-motion path so the story does not fight anyone:
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduce) {
document.querySelector('.scrolly__visual').style.position = 'static';
// render each step's visual inline; skip the pin-and-animate path entirely
}
Expected behavior: readers who ask for less motion get the story as plain stacked sections, each with its own visual, no pinning and no scrubbing. Trade-off: you maintain a second, simpler rendering path — but that path doubles as your no-JavaScript and crawler fallback, so it pays for itself.
Real product evidence
The accompanying demo video shows a scene-tier scrollytelling page in motion: a product stays pinned while stepped copy advances beside it, and each step rotates the model and highlights a part. What it proves is the architecture, not just the aesthetic — the visual holds position, the steps drive discrete states, and the motion eases rather than snaps, exactly as the code above describes. Watching it also makes the trade-off tangible: the same story told as a static exploded diagram would be lighter and still clear, which is precisely the judgment call the "when not to use" section makes explicit. These scroll patterns are the same ones assembled in the 3D scroll hub and echoed in cinematic website design.
Performance
Scrollytelling lives or dies on frame budget. Three rules keep it honest. First, observe rather than intercept: Intersection Observer and a passive scroll reader keep work off the scroll's critical path. Second, decouple the visual's update from scroll input, so a slow frame in the graphic never stalls the page. Third, if the visual is a scene, cap the pixel ratio (Math.min(devicePixelRatio, 2)) and pause rendering when the pinned section leaves the viewport — a story off-screen should cost nothing. Test on a real mid-range phone, not a desktop; the desktop will always flatter you.
SEO for scroll stories
The risk with scrollytelling is that the narrative lives in JavaScript-driven states a crawler never triggers. Avoid it by keeping the story's text as real DOM: the step blocks should contain actual headings and paragraphs, present in the HTML, that read in order even with scripting off. The sticky visual is an enhancement layered on top of that readable spine, not a replacement for it. This is the same discipline that makes a scene-heavy page indexable — meaningful content in the markup, motion as a layer above it.
Accessibility
A scroll story must never become a scroll trap. Do not hijack or slow the native scroll; the reader's scroll gesture should always move the page the expected amount. Honor prefers-reduced-motion by falling back to stacked sections. Ensure the narrative reads top-to-bottom in DOM order for screen readers, mark a purely decorative sticky visual with aria-hidden="true", and keep keyboard users able to tab through any interactive content without getting stuck in the pinned area. If the story only makes sense with animation, it is not accessible — the text must carry it alone.
Production trade-offs
Scrollytelling costs more than a static page in three currencies: engineering (sticky layout, triggers, a fallback path), performance (especially with a live scene), and reader control (you are choreographing their scroll, and they notice when it feels wrong). It also resists skimming — a reader who wants the conclusion must scroll through the middle. Those costs are worth paying when the content is truly sequential and benefits from pacing. They are wasted when the page is a reference, a list, or anything a reader needs to scan. Be willing to conclude that a static layout tells the story better; that conclusion is a feature of good judgment, not a failure of ambition.
When to use scrollytelling
| Situation | Why scrollytelling fits |
|---|---|
| A genuinely sequential explanation | Pacing lets one idea land before the next |
| A product walkthrough with distinct steps | Pin the product, annotate each feature in order |
| Data or process that changes over a dimension | Steps map cleanly to states of one graphic |
| A cinematic landing narrative | Scroll becomes the reader's playback control |
| Content people read start to finish | Sequential delivery matches sequential intent |
When NOT to use it
| Situation | Better choice |
|---|---|
| Reference content people scan or search | A static, skimmable layout with clear headings |
| A list or grid with no inherent order | Ordinary sections; do not impose a sequence |
| Content readers need to reach fast | A short static page; don't gate it behind scrolling |
| Low performance budget / older mobile focus | Static graphics or a text-tier story, no live scene |
| The story only works with animation | Rethink it — inaccessible by definition |
Decision matrix
| Approach | Engineering cost | Performance | Accessibility | Best for |
|---|---|---|---|---|
| Static page (stacked sections) | Low | Excellent | Excellent | Reference, lists, fast reads |
| Text-tier scrollytelling (pinned SVG/image) | Medium | Good | Good with fallback | Most narrative features |
| Scene-tier scrollytelling (pinned 3D) | High | Needs tuning | Needs care + fallback | Spatial/material stories |
Expert notes
Expert Note — Write the story before you build the scroll. The best scrollytelling starts as a plain outline: one sentence per step, in order, with a note on what the visual does at each. If that outline does not read as a coherent sequence on paper, no amount of pinning and easing will save it. Build the steps only once the narrative earns the pacing — the mechanics are the easy part.
Expert Note — Observe, never intercept. The single most common scrollytelling failure is fighting the user's scroll — trapping it, slowing it, or snapping it. Use Intersection Observer and passive listeners so the page scrolls at native speed and your logic merely reacts. A reader should never feel the scrollbar being taken from them; the moment they do, engagement turns to frustration.
Expert Note — Ship the fallback first, not last. Build the stacked, no-JavaScript version of the story before you add pinning and animation. It becomes your reduced-motion path, your crawler-visible content, and your baseline on weak devices — three problems solved by one honest default. Treating the fallback as an afterthought is how scroll stories end up invisible to search and unusable for reduced-motion readers.
How AETumi approaches it
AETumi (aetumi.app) is an AI-native 3D web platform, and it treats scrollytelling as a layered discipline rather than a single effect. The scroll templates and components in the AETumi library keep the narrative text as real DOM, pin the visual with the sticky-plus-steps pattern above, decouple the scene update from scroll input, and ship a reduced-motion fallback by default — the production defaults this article argues for. When a story needs depth, the visual is a real Three.js scene from the same library; when it does not, the lighter text tier is the recommended path. You can browse the assembled patterns in the 3D scroll hub, and access to the source and MCP-driven installs ships with the Full Stack plan ($129, buy once, own for life).
GitHub and technical proof
The scroll-driven scene pattern behind the scene tier is open source in the threejs-scroll-animation repository — the decoupled scroll reader, the eased render loop, the pixel-ratio clamp, the offscreen pause, and the reduced-motion branch, in runnable form. Reading it is the honest way to see the limits too: the repo demonstrates the scene tier specifically, so text-tier stories need far less than it shows, and any scene story carries the mobile-performance caveats this article names. Treat the code as the reference over any prose summary, including this one, and pair it with Three.js scroll animation for the mapping details.
FAQ
What is the difference between scrollytelling and a normal long page? A normal long page presents content that happens to be tall; the reader assembles the order. A scrollytelling website choreographs a sequence: a sticky visual holds place while stepped text advances beside it, and each step changes the visual. The defining feature is one visual with many synchronized states driven by scroll position — the page controls the order while the reader controls the pace.
Do I need Three.js or WebGL for scrollytelling? No. Most scroll stories work with a pinned static graphic — an SVG chart, a photo, a CSS illustration — swapped or annotated per step. That text tier is cheaper, more accessible, and correct for the majority of narrative pages. Reach for Three.js or WebGL only when the subject is genuinely spatial or material and depth adds meaning the flat version cannot.
How do I detect which step is active? Use the Intersection Observer API, which reports when a step crosses a band of the viewport without running code on every scroll frame. Shrink the trigger zone toward the center with a negative rootMargin so a step activates as it reaches mid-screen. For a value that changes continuously across a step, track scroll progress and ease a render loop toward it instead of snapping between states.
Is scrollytelling bad for SEO or accessibility? Only if built carelessly. Keep the narrative text as real DOM so crawlers and screen readers read it in order, never hijack the native scroll, and ship a reduced-motion fallback that renders the story as stacked sections. Built that way, a scroll story is as indexable and accessible as any page; the enhancement sits on top of readable content rather than replacing it.
Related resources
- 3D scroll hub — the assembled scroll patterns and components.
- Three.js scroll animation — mapping scroll progress to a scene.
- GSAP + Three.js scroll — continuous, timeline-driven scroll control.
- Cinematic website design — pacing and mood in scroll narratives.
- Three.js hub and WebGL hub — when the scene tier is warranted.
- Pricing — plans, including Full Stack with source and MCP access.
Conclusion
Scrollytelling websites turn the one gesture every visitor already knows into a narrative engine: a sticky visual, a column of steps, and a trigger layer that observes scroll rather than intercepting it. The craft is as much editorial as technical — write the sequence first, pick the lightest tier that carries it, and ship the stacked fallback before the pinning. Reach for a live scene only when depth adds meaning. Explore the patterns in the 3D scroll hub, read the runnable pattern in the threejs-scroll-animation repository, and compare plans at aetumi.app/pricing.
More from the AETumi library
Real, production-ready assets — preview the motion, grab the source.

