/

Scroll Animations in React: Reveal, Pin and Parallax Without Jank

Four scroll mechanisms, the components for each, and the five implementation habits that turn a scroll-driven page from considered into broken.

Serafim Korablev
Serafim Korablev
@korablev

Scroll is the only input every reader gives you for free. Tying something to it is the cheapest way to make a page feel considered, and the fastest way to make it feel broken, and the line between the two is almost always the same thing: whether the reader is still in control of the page.

The scroll-driven family is the most-bookmarked group in the catalogue. Sorted by mechanism rather than by look, there are four, and they fail differently.

Reveal

Content appears as it enters the viewport. The safest of the four, because nothing about the page's behaviour changes: the reader scrolls at their own speed and things fade in.

Text Reveal by dillionverma is the typographic version, described by its author as fading in text as you scroll down the page.

The implementation detail worth knowing: this is an IntersectionObserver, not a scroll listener. An observer fires when an element crosses a threshold and costs nothing in between; a scroll handler runs on every frame whether anything changed or not. Almost every reveal you will read about in an old tutorial is written the expensive way.

One rule: reveal on entry, never re-hide on exit. A reader scrolling back up should not watch the page dismantle itself.

Parallax

Layers moving at different rates to imply depth. Hero Parallax by Manu Arora is the rows-of-product-shots version, Parallax Scrolling by osmosupply the general one, and Zoom Parallax by Efferd the cinematic take where images zoom at different speeds.

Parallax Floating by danielpetho is the pointer-driven cousin: elements drift with the cursor rather than with the scroll, which works on desktop and does nothing on touch.

Parallax is where transform: translate3d earns its keep. Animating top or background-position moves work onto the main thread; animating a transform stays on the compositor. On a long page with several layers, that is the difference between smooth and juddering on a phone.

Pin and progress

The scroll position drives a timeline: an element sticks while the page continues, and the movement is a function of how far through the section you are. Container Scroll Animation by Manu Arora is the canonical one, a panel that rotates in 3D as you scroll. Story scroll by Samira Boudjadja and Full Screen Scroll FX by scott clayton are the narrative versions.

The honest warning: pinning is where scroll-driven pages start to feel wrong. A section that holds the viewport for four screen-heights has taken the reader's scroll and spent it, and if the payoff is a headline fading in, the trade was bad. Budget pinned sections like you budget modals.

Scroll as the transition

The largest version: the hero is not a layout, it is the movement into the page. Scroll media expansion hero by Arunachalam expands a media panel as you scroll, Scroll Morph Hero by Prashant Som morphs a shape, and Smooth Scroll Hero by Shamsu Musthafa is the calmer one where parallax does the work and nothing resizes.

These are covered in more depth in the hero section guide. The relevant note here is that they are the pattern most likely to be combined with smooth scrolling, which is the part that needs a decision rather than a default.

The five ways this goes wrong

A scroll listener on every frame. window.addEventListener("scroll", ...) runs on the main thread during the most performance-sensitive moment there is. Use IntersectionObserver for enter and exit, and CSS scroll-driven animations (animation-timeline: view()) where they are supported, which run off the main thread entirely.

Hijacked scrolling. A page that intercepts the wheel and animates to a position has taken away the one control the reader had. Smooth-scrolling libraries do this by design: they replace native scrolling with a lerped transform. They can feel wonderful on a trackpad and terrible on a mouse wheel, they break Ctrl+F scrolling on some setups, and they interact badly with keyboard navigation. If you ship one, test with a mouse wheel, with Page Down, and with a screen reader before deciding it is fine.

Content that only exists after a scroll. If the text is added to the DOM by the animation, then a crawler, an answer engine and anyone with JavaScript disabled see an empty section. Render the content, animate its appearance. The difference is opacity: 0 versus not being there.

No reduced-motion path. Every effect in this article is motion. Under prefers-reduced-motion: reduce, reveals should render visible immediately and parallax should be flat. This is a media query, not a feature request.

Layout thrash from measurement. Reading getBoundingClientRect inside a scroll handler and then writing a style forces a synchronous layout on every frame. Measure once, cache, and recalculate on resize.

Where else to look

The honest list of alternatives, because the answer is not always us:

SourceBest forTrade-off
CSS scroll-driven animationsReveals and progress with zero JavaScript, off the main threadBrowser support is still uneven; needs a fallback
IntersectionObserverEnter and exit, which is most of what reveals needNot a timeline: no "how far through" value
GSAP ScrollTriggerPinning, scrubbed timelines, complex sequencesThe heaviest option, and pinning is easy to overuse
MotionuseScroll and useTransform inside a React mental modelA runtime for something CSS increasingly does
21stThe finished effects: zoom parallax, morphing heroes, story scrollsQuality varies by author, so preview before you take it

Taking one

Every component page has a live preview and the code. Installing goes through the shadcn CLI against our registry:

bash

That key comes from your 21st account, and installs require a membership. Set API_KEY_21ST once in your shell and the command works for anything in the catalogue. The 21st MCP covers the same ground without leaving the editor: ask Claude, Cursor or Codex for a scroll animation, get the previews inline, and let the agent write the file.

Browse scroll animation components →

Frequently asked

How do you animate on scroll without hurting performance?
Use IntersectionObserver for enter and exit rather than a scroll listener, because an observer fires on a threshold crossing and costs nothing in between while a scroll handler runs on every frame. For progress-driven effects, prefer CSS scroll-driven animations with animation-timeline, which run off the main thread, and animate transforms rather than top or background-position.
Is smooth scrolling worth it?
Only with testing. A smooth-scrolling library replaces native scrolling with an interpolated transform, which can feel excellent on a trackpad and wrong on a mouse wheel, interacts badly with keyboard navigation, and breaks find-in-page scrolling in some setups. If you ship one, try it with a wheel, with Page Down and with a screen reader before deciding.
Do scroll-revealed sections get indexed?
Yes, if the content is in the HTML and only its appearance is animated. What breaks indexing is adding the text to the DOM inside the animation, so the markup that arrives from the server contains an empty section. The difference is starting at opacity zero versus not being there at all.
What should scroll animations do under reduced motion?
Reveals should render visible immediately and parallax should be flat. Every effect in this family is motion, so prefers-reduced-motion: reduce needs a real branch, not a slower duration. Pinned sections are the ones to reconsider entirely: holding the viewport for several screen heights is a large ask even for readers who did not opt out.

Published

Aug 20, 2026

Read time

6 min

Tags

GuideAnimationReactPerformance

Share