04 / blog / guideJune 202615 min read

The complete guide to scroll animations in 2026

Every scroll animation pattern — fade reveal, parallax, sticky pin, section snap, text split, video scrub, horizontal sections, and CSS variable binding — explained with copy-paste code. All examples use svg-scroll-draw, a single ~12 KB library that covers all of them.

  • Guide
  • Cornerstone
  • Every pattern
  • Copy-paste code
Patterns
12
Main entry
11.6 KB
Dependencies
zero
Updated
October 2026
Fig. 1 — one product landing page, every pattern in this guide marked where it is used — the key links to each section
01scrollReveal

Fade reveal — the universal pattern

The most common scroll animation: elements fade up into view as the user scrolls past them. Every major website uses this. AOS and ScrollReveal.js popularised it. svg-scroll-draw does it in one call with scrollReveal.

Every snippet in this guide runs in the browser. In the Next.js App Router, put it in a client component and keep the page on the server; the Next.js scroll animation guide has a complete App Router project to copy.

app.js15 lines
import { scrollReveal } from 'svg-scroll-draw/reveal';

// Default: fade up (opacity 0→1, translateY 32→0)
scrollReveal('.card');

// Custom from state
scrollReveal('.feature', {
  from:    { opacity: 0, y: 40, scale: 0.96 },
  stagger: 0.1,
  easing:  'ease-out',
  once:    true,
});

// Named presets: fadeUp|fadeDown|fadeLeft|fadeRight|scale|flip|flipX
scrollReveal('.badge', { preset: 'scale' });
02scrollParallax

Parallax

Move elements at a different rate than scroll. Background images, floating badges, decorative shapes. A positive speed moves the element up as you scroll down, ahead of the page; a negative one moves it down, so it lags behind.

app.js5 lines
import { scrollParallax } from 'svg-scroll-draw';

scrollParallax('#hero-bg',       { speed: 0.4 });   // ahead of the page: up 40% of its height
scrollParallax('#floating-badge',{ speed: -0.2 });  // lags behind the page
scrollParallax('#side-element',  { speed: 0.3, axis: 'x' }); // horizontal
03scrollAnimate

Animate any CSS property on scroll

Not just opacity and transform — any CSS property can be driven by scroll position. Colors, backgrounds, borders, clip-paths, filter, font-size, anything.

app.js21 lines
import { scrollAnimate } from 'svg-scroll-draw';

// Color transition (section background shifts as you scroll through)
scrollAnimate('#section', {
  props: {
    backgroundColor: ['#ffffff', '#0d0d0d'],
    color:           ['#000000', '#ffffff'],
  },
});

// Border radius morph
scrollAnimate('#card', {
  props: { borderRadius: ['0px', '24px'] },
});

// Blur reveal
scrollAnimate('#image', {
  props: { filter: ['blur(20px)', 'blur(0px)'], opacity: [0.3, 1] },
  easing: 'ease-out',
  once:   true,
});
04scrollPin

Sticky / pin sections

The Apple product page pattern. An element stays fixed while content scrolls past it. Use scrollPin — it wraps the target in a spacer so the page layout doesn’t jump.

app.js11 lines
import { scrollPin } from 'svg-scroll-draw/pin';

// Pin product image while feature text scrolls
scrollPin('#product-image', {
  top:         80,                     // 80px from viewport top (below nav)
  pinDistance: '300vh', // stay pinned for 3 viewport heights
  onEnter:     () => image.classList.add('active'),
  onLeave:     () => image.classList.remove('active'),
  onEnterBack: () => image.classList.add('active'),
  onLeaveBack: () => image.classList.remove('active'),
});
05scrollSnap

Section snapping

Snap the viewport to the nearest section when the user stops scrolling. Custom easing, configurable threshold, programmatic control.

app.js13 lines
import { scrollSnap } from 'svg-scroll-draw/snap';

const snap = scrollSnap('.section', {
  duration:  600,
  easing:    'ease-in-out',
  threshold: 0.3,            // snap if scrolled 30%+ past a section
  onSnap:    (index) => updateNav(index),
});

// Programmatic
snap.snapTo(2);              // smooth scroll to section 2
snap.getCurrentIndex();      // → active section index
snap.destroy();
06scrollText

Text split + reveal

Split text into chars, words, or lines and stagger-animate each piece. A 3.4 KB alternative to GSAP SplitText. SplitText is free and 3.6 KB, but it only splits — animating the pieces needs GSAP core (27.7 KB) too.

app.js19 lines
import { scrollText } from 'svg-scroll-draw/text';

// Word-by-word headline reveal
scrollText('#headline', {
  split:   'words',
  stagger: 0.07,
  from:    { opacity: 0, y: 32 },
  easing:  'ease-out',
  once:    true,
});

// Typewriter (char by char)
scrollText('#subtitle', {
  split:   'chars',
  stagger: 0.015,
  from:    { opacity: 0 },
  easing:  'linear',
  once:    true,
});
07scrollCounter

Animated counters

Numbers that count up as they scroll into view. Stats sections, pricing, social proof.

app.js6 lines
import { scrollCounter } from 'svg-scroll-draw';

scrollCounter('#users',      { to: 50000, format: n => Math.round(n).toLocaleString() + '+' });
scrollCounter('#revenue',    { to: 1250000, format: n => '$' + Math.round(n).toLocaleString() });
scrollCounter('#nps',        { to: 94.7, format: n => n.toFixed(1) + '%' }); // decimals would replace format
scrollCounter('#bundle',     { to: 12, format: n => '~' + Math.round(n) + ' KB' });
08scrollVideo

Video scrubbing

Tie <video>.currentTime to scroll position. The Apple / Stripe product video pattern — free, no GSAP needed.

app.js5 lines
import { scrollVideo } from 'svg-scroll-draw/video';

scrollVideo('#hero-video', {
  trigger: { start: 'top top', end: 'bottom top' },
});
09scrollHorizontal

Horizontal scroll sections

Vertical scroll drives horizontal movement. Set up sticky CSS, one call drives the transform.

app.js8 lines
import { scrollHorizontal } from 'svg-scroll-draw/horizontal';

// CSS: .outer{height:400vh} .sticky{position:sticky;top:0;height:100vh;overflow:clip}
// .track{display:flex;width:max-content}

// The travel is measured from the track and its sticky stage, and again
// whenever the layout changes.
scrollHorizontal('.track');

The whole thing as a recipe, a case-study strip in five steps with its React and HTML files and where it goes wrong, is in Horizontal scroll sections without GSAP.

10scrollProgress

CSS variable binding

Expose scroll progress as a CSS custom property. Then CSS calc() drives everything — color, size, position, filter — with zero extra JS.

app.js3 lines
import { scrollProgress } from 'svg-scroll-draw/progress';

scrollProgress('#section', { easing: 'ease-in-out' });
styles.css9 lines
#section {
  /* Drive any CSS property from the variable */
  opacity: calc(var(--scroll-progress-eased));
  transform: translateY(calc((1 - var(--scroll-progress-eased)) * 40px));
  background: hsl(
    calc(240 + var(--scroll-progress) * 120),
    60%, 10%
  );
}
11onEnter · onLeave

Scroll callbacks (onEnter / onLeave)

Fire code when scroll crosses the trigger zone — in either direction. Nav highlighting, lazy loading, analytics events, state updates.

app.js10 lines
import { scrollAnimate } from 'svg-scroll-draw';

scrollAnimate('#section', {
  props: { opacity: [0.4, 1] },
  trigger: { start: 'top center', end: 'bottom center' },
  onEnter:     () => nav.setActive('section'),
  onLeave:     () => nav.clearActive('section'),
  onEnterBack: () => nav.setActive('section'),
  onLeaveBack: () => nav.clearActive('section'),
});
12animation-timeline

Native CSS scroll-driven animations

When animation-timeline: view() is supported (Chrome and Edge 115+, Safari 26+, Firefox 159+), svg-scroll-draw automatically uses it — zero JS scroll listeners, zero rAF; for opacity and transform the browser can run it on the compositor. Falls back silently in older browsers.

app.js14 lines
import { scrollAnimate } from 'svg-scroll-draw';

// native: true is the default — uses CSS fast path when eligible
scrollAnimate('#hero', {
  props: { opacity: [0, 1], transform: ['translateY(32px)', 'translateY(0)'] },
  // native: true (default) — CSS scroll timeline when supported; opacity and transform can run on the compositor
});

// Force JS engine (needed for callbacks, velocity, custom easing fns)
scrollAnimate('#section', {
  props: { opacity: [0, 1] },
  native: false,
  onEnter: () => activate(),
});

What makes a config eligible, and what the native path saves, is in Zero-JS SVG scroll animations with native CSS.

13Parts list

One library, all patterns

APIImport fromPattern
scrollRevealsvg-scroll-draw/reveal01 · Fade reveal
scrollParallaxsvg-scroll-draw02 · Parallax
scrollAnimatesvg-scroll-draw03 · Any CSS property
scrollPinsvg-scroll-draw/pin04 · Sticky / pin
scrollSnapsvg-scroll-draw/snap05 · Section snapping
scrollTextsvg-scroll-draw/text06 · Text split + reveal
scrollCountersvg-scroll-draw07 · Animated counters
scrollVideosvg-scroll-draw/video08 · Video scrubbing
scrollHorizontalsvg-scroll-draw/horizontal09 · Horizontal sections
scrollProgresssvg-scroll-draw/progress10 · CSS variable binding
onEnter / onLeavesvg-scroll-draw11 · Scroll callbacks
scrollDraw · scrollAnimatesvg-scroll-draw12 · Native CSS timelines
scrollDrawsvg-scroll-drawSVG path drawing — the library’s namesake
devtoolssvg-scroll-draw/devtoolsA development overlay of each instance’s trigger window

— / next

One library, all patterns

Each pattern is its own entry point, so you ship only what you import — scrollPin alone is 1.6 KB, scrollReveal 5.8 KB, the main entry 11.6 KB. MIT, zero dependencies.

→Parts list

Keep reading

More from the blog