A working field guide to scroll-driven motion17 specimens · scroll to begin

Every specimen below runs live on this page. Each one names the driver behind it, the properties it animates, and whether it is triggered once or scrubbed with your scroll. The pill at the bottom shows the current progress value p.

Reveal on enter

Tiles start faded and offset, then settle when they cross into view. Scroll back up past them and they reset, so every pass replays the entrance with a 70 ms stagger.

Driver
IntersectionObserver
Animates
opacity, transform, filter, clip-path
Mode
triggered

Rise

translateY(56px) → 0

Slide in

translateX(-70px) → 0

Grow

scale(.72) → 1

Focus

blur(14px) → 0

Tilt

rotate(-9deg) → 0

Wipe

inset(0 88% 0 0) → 0

Hinge

rotateX(70deg) → 0

Drift

translateX(70px) → 0

Parallax depth

Each layer moves at a speed set by its depth value. Negative depth drifts against the scroll and reads as far away. Values above 1 outrun the page and read as close to you.

Driver
scroll progress × depth
Animates
transform: translate3d
Mode
scrubbed
DEPTHdepth −0.4
depth 0.35
depth 0.8
depth 1.5
nearest layer
depth 2.3

Word by word

The paragraph stays pinned while you read. Each word owns a slice of the scroll range and brightens as you move through it.

Driver
position: sticky + p
Animates
opacity per word
Mode
scrubbed

Scroll is the one input every visitor already uses. Tie motion to it and the [reader sets the pace.] They can scrub forward, scrub back, or stop anywhere, and the animation never runs ahead of the reading.

Counting up

Numbers tween from zero with an exponential ease-out when the row enters view. The figures are the frame budgets every scroll effect on this page has to fit inside.

Driver
IntersectionObserver + rAF
Animates
textContent
Mode
triggered
60fps

The refresh rate most displays target.

16.7ms

Time for one frame at 60 Hz, including the browser's own work.

120Hz

High-refresh phones and laptops double the frame rate.

8.3ms

What's left per frame at 120 Hz. Read once, then write.

Horizontal track

Vertical scroll drives a sideways track. Every card plots the same progress value through a different easing curve, so you can compare them at a single point in time.

Driver
position: sticky + p
Animates
transform: translateX
Mode
scrubbed
Same p, eight curves

One input, many outputs.

The orange dot sits at your current progress on every curve. Watch where each one is ahead of, or behind, the straight line.

Velocity marquee

Speed and lean come from how fast you scroll, not where you are. Flick the wheel hard, then stop, and watch it ease back to cruising speed. Scrolling up reverses it.

Driver
scroll delta per frame
Animates
translateX, skewX
Mode
velocity
SCROLL FASTER ● SCROLL FASTER ●
THE TYPE LEANS IN ● THE TYPE LEANS IN ●

velocity 0.0 px/frame · skew 0.0°

Route drawing

A trail draws itself as you scroll. The line is one SVG path with its dash offset tied to progress, and the walker is placed with getPointAtLength().

Driver
position: sticky + p
Animates
stroke-dashoffset, cx/cy
Mode
scrubbed
0.0 km of 12.6 km · 1,240 m

Stacking cards

Each new card slides over the last, which shrinks and dims underneath. The cards list what animating each property costs the browser, cheapest first.

Driver
position: sticky + p
Animates
transform, filter
Mode
scrubbed

transform

Composite only

Move, scale and rotate on the GPU without touching layout or paint. The default choice for anything tied to scroll.

LayoutPaintComposite

opacity

Composite only

Fades an element's layer in place. Pair it with transform and most entrance effects stay off the main thread.

LayoutPaintComposite

filter

Paint, often composited

Blur, brightness and saturation. Fine in small doses; large blur radii on big surfaces get expensive quickly.

LayoutPaintComposite

clip-path

Paint

Reveals and masks. Simple shapes such as circle() and inset() stay fast; complex polygons cost more.

LayoutPaintComposite

width, top

Layout

Changing geometry moves everything after it on the page. Keep these out of scroll handlers and use transform instead.

LayoutPaintComposite

Aperture

A circular clip-path opens from the center and uncovers a second layer sitting directly on top of the first. The radius grows until it reaches the farthest corner.

Driver
position: sticky + p
Animates
clip-path: circle()
Mode
scrubbed

Small
openings

become
whole rooms.

circle(0px at 50% 50%)

Rotating cube

Scroll maps straight onto two rotation angles of a preserve-3d box. Six faces, one transform, no 3D library.

Driver
position: sticky + p
Animates
rotateX, rotateY
Mode
scrubbed
FrontrotateY 0°
RightrotateY 90°
BackrotateY 180°
LeftrotateY 270°
ToprotateX 90°
BaserotateX −90°
rotateX(-18°) rotateY(0°)

Morning sky

The background interpolates between seven sky colors sampled from 05:00 to 11:00, and the sun climbs with your progress. Text flips between light and dark from the color's luminance so it stays readable.

Driver
position: sticky + p
Animates
background-color, transform
Mode
scrubbed
05:00
#141B33 · before dawn

Dot field

A wavefront sweeps out from the lower-left corner across a grid of dots. Each frame, every dot is redrawn with a size set by its distance to the wave.

Driver
position: sticky + p
Animates
canvas 2D redraw
Mode
scrubbed
wavefront r = 0 px

Zoom through

The word scales exponentially around the exact center of its O, so the hole grows until it fills the screen and you pass through into the next scene.

Driver
position: sticky + p
Animates
transform: scale, transform-origin
Mode
scrubbed

You're inside the O.

scale(1.00)
THRUGH

Frame pipeline

The stages a browser runs to show one scrolled frame, in order. Progress is cut into six equal steps, and the rail fills continuously between them.

Driver
position: sticky + ⌊p × 6⌋
Animates
class swap, height
Mode
stepped

Native timelines

No JavaScript in this one. CSS animation-timeline: view() ties a plain keyframe animation to each tile's position in the viewport, and the bar below follows the whole page with scroll(root).

Driver
CSS scroll-driven animations
Animates
opacity, transform
Mode
scrubbed
Checking browser support…
.tile {
  animation: natIn linear both;
  animation-timeline: view();
  animation-range: entry 0% cover 42%;
}
.bar {
  animation: grow linear both;
  animation-timeline: scroll(root);
}
/* Runs off the main thread. */
view
()
entry
cover
exit
contain
0%
50%
100%

Exploded robot

A humanoid robot built from 28 rounded parts comes apart as you scroll, the way an exploded view in a repair manual does. Shells lift off first to show the battery, frame and actuators underneath, and dashed guide lines trace where each part came from. Scroll back up and it puts itself together.

Driver
position: sticky + p → Three.js
Animates
part positions, guide lines, camera orbit
Mode
scrubbed
0 of 28 parts detached Scroll down to take it apart.
Scroll up to rebuild it.

The 3D library didn't load, so this specimen can't run here.

Self-solving cube

A puzzle cube scrambled with 20 random turns solves itself as you scroll. The solution is the scramble played backwards: each slice of the scroll range turns one face a quarter or half turn. Scroll up and it scrambles again.

Driver
position: sticky + ⌊p × 20⌋
Animates
26 cubie positions and quaternions
Mode
stepped
Scrambled · 20 turns to solve

The 3D library didn't load, so this specimen can't run here.