Dolly 34 moves · 0 kB of JavaScript
scroll
GitHub
Image
Image
Image
Image
Image
Image

Nobody is filming your page.

There is no camera, so every camera move on the web had to be invented. Dolly invents 34 of them in CSS, and scrolling back up rewinds them.

An audience seated in a dark cinema facing a bright screen
zoom · pinned scene

Fourteen kilobytes, watching a scrollbar.

AOS ships 14 kB of JavaScript to notice where you are. Dolly ships a stylesheet, and a stylesheet cannot fail to boot.

kB

count · @property + counter()

D O L L Y

It is named after a cart. A dolly carries a camera through a shot and never appears in one. Sibling to Foley, which does the sound.

push · five letters, five depths
Image
Image
Image
Image
Image

You already know these words.

Cinema named these moves before the web existed. Knowing what they do is most of the documentation.

tiltpancranerackwhip rolliristrackmattezoom parallaxdolly indolly outwipe
track · vertical scroll, horizontal travel

Every film ever made is a flipbook.

This one is thirty-six pictures on a single sheet. Scrolling picks which one you see — no canvas, no decode loop, nothing running. The browser is moving a background by one cell.

Dolly Parton
Dolly Parton
sequence + iris · one sheet, thirty-six cells

What it refuses to do.

"tilt-up"

It never hides your content.

AOS, ScrollReveal and sal all put opacity: 0 in a base rule and reveal it from script. Block the script and the page is blank. Dolly puts the hidden state only in @keyframes, so no support means it didn't animate, never it didn't appear. The worst it can do is nothing.

Nothing runs, so nothing runs late.

No IntersectionObserver, no scroll listener, no rAF loop, no init call, no provider, no hydration. There is no boot sequence to wait on and no script tag to block — including for the pinned chapters you just scrolled through.

Reduced motion means off, not slower.

Under prefers-reduced-motion every move switches off, scenes stop pinning, and the page renders exactly as authored — because the authored state was never the hidden one.

Firefox is fine, today.

Scroll timelines run in Chrome 115+, Edge, Safari 26+ and Opera — about 87% of the web, with Firefox landing in 159. The other 13% get a complete, still page, which is the design, not the degradation. There is no shim to ship.

The full set.

the reference
MoveRangeWhat it does
fadeopacity only
tilt-uprises into place
tilt-downdrops into place
pan-leftslides in from the right
pan-rightslides in from the left
dolly-incamera pushes toward it
dolly-outcamera pulls back from it
cranelifts and settles
rackfocus pulls from blurred
whipfast pan with motion blur
revealwipes in horizontally
wipe-upwipes in vertically
rollrotates level
progressnormaltracks the whole page
travelnormalrides page scroll
parallaxcoverdrifts against the scroll
zoomcoverslow continuous push
dropcontain 0% contain 100%falls through the frame
passcontain 0% contain 100%tracks past, reversing its yaw
pushthe camera travels toward it
pullthe camera draws back
swingrotates in on Y
tumblepitches in on X
flycontain 0% contain 100%travels from far, past the camera
beatarrives, holds, leaves
beat-indefocuses in, holds, defocuses out
holdarrives and stays
irisan aperture opening
trackcontain 0% contain 100%vertical scroll, horizontal travel
drawentry 0% cover 60%a stroke drawing itself; needs pathLength="1"
countentry 0% cover 45%a number counting up, printed by counter()
sequencecover 0% cover 100%a sprite strip, stepped one frame per slice of scroll
maskfootage inside letterforms
lettertracking closes as it arrives

There is no step two.

one attribute, plus scenes

Link the stylesheet, then annotate markup you already have. No init call, no wrapper component, no configuration object, because nothing is running that could read one.

index.html nothing else to install
<link rel="stylesheet" href="dolly.css">

<h1 data-dolly="tilt-up">Arrives as you reach it</h1>
<img data-dolly="rack" src="still.jpg" alt="">

<!-- a pinned scene: the chapters above are all this -->
<div data-dolly-scene>
  <div data-dolly-pin>
    <img data-dolly="zoom" data-dolly-on="scene" src="shot.jpg" alt="">
  </div>
</div>

/* one caveat: a parent with overflow:hidden becomes its own scroll
   container and pins the move at its end state. Use overflow:clip. */
npm i @dollyjs/core

that is the whole library

34 moves · 0 kB of JavaScript · one attribute

Dolly is named for the camera dolly, the cart that carries a camera through a shot. The name came first and stands on its own. It is also, this year, a word that is hard to write down without thinking of someone else. Dolly Parton's Imagination Library has sent a book to a child every month since 1995, and still sends them. It takes donations. This library does not.

1946–2026