motion-scene
Scroll-driven animation timeline. Pin a stage while scroll progress drives children through keyframes. It's purely declarative, using data attributes.
Live showcase → Declare · Sequence · ComposeHow it works
Wrap a scene in a tall container. The inner stage sticks while the user scrolls. Children with data-from/data-to are scrubbed from their start keyframe to their end keyframe as scroll advances through data-start–data-end (0–1 progress values).
Preview
Scroll inside the box to drive the animation.
Import
import 'motion-components/motion-scene' // just this one
import 'motion-components' // or the whole library<motion-scene height="300vh">
<h2
data-from='{"opacity":0,"y":"-30px"}'
data-to='{"opacity":1,"y":"0px"}'
data-start="0"
data-end="0.4"
>
Appears first
</h2>
<p
data-from='{"opacity":0}'
data-to='{"opacity":1}'
data-start="0.3"
data-end="0.7"
>
Fades in mid-scroll
</p>
<span
data-from='{"opacity":0,"scale":"0.8"}'
data-to='{"opacity":1,"scale":"1"}'
data-start="0.6"
data-end="1"
>
Scales in last
</span>
</motion-scene>CSS variable
The scene exposes --mc-progress (0–1) so you can drive your own CSS effects off scroll position.
motion-scene {
--hue: calc(var(--mc-progress) * 200);
background: hsl(var(--hue) 40% 8%);
}Custom scroll container
By default the scene tracks document scroll. Pass a CSS selector via container to use an overflow element instead.
<div id="my-scroll-box" style="height:500px;overflow-y:auto">
<motion-scene height="200%" container="#my-scroll-box">
<h2
data-from='{"opacity":0}'
data-to='{"opacity":1}'
data-start="0"
data-end="0.5"
>
Tracks the box scroll
</h2>
</motion-scene>
</div>Without pinning
pin defaults to true. Set it to "false" to get scrubbed
children without a sticky stage.
<motion-scene height="200vh" pin="false">
<div
data-from='{"scale":"0.8","opacity":0}'
data-to='{"scale":"1","opacity":1}'
data-start="0"
data-end="1"
>
Scales in over the full scroll
</div>
</motion-scene>Properties
| Attribute | Property | Type | Default | Description |
|---|---|---|---|---|
| | | string | '200vh' | Height of the scroll container: controls how much scroll the scene consumes. |
| | | boolean | true | The inner stage sticks to the viewport while scrolling. Set it to |
| | | string | '' | CSS selector for a custom scroll container. Defaults to document scroll when empty. |
Child data attributes
| Property | Type | Default | Description |
|---|---|---|---|
| | JSON | — | Starting keyframe, e.g. |
| | JSON | — | Ending keyframe, same shape as |
| | number | 0 | Scene progress (0–1) at which this child's animation begins. |
| | number | 1 | Scene progress (0–1) at which this child's animation ends. |
JavaScript API
Control the scene programmatically with play(), pause(), finish(), and cancel(). Pause unbinds the scroll listener so the card freezes mid-scrub. Finish snaps to the end keyframe. Cancel resets to the start. Scroll the box while running to drive the card.
Accessibility
Respects prefers-reduced-motion: when enabled, all scroll-driven animations are disabled and children render at their end state immediately.