Scroll-driven animation
Bind animation progress to a scroll range and tune its start, end, and travel.
Live preview
An animation whose progress is tied directly to scroll position.
Definition and close-term distinctions
English definitions are preserved verbatim from Animation Vocabulary; translations and distinctions help you choose precisely.
scroll-driven-animationScroll-driven animation
滚动驱动动画An animation whose progress is tied directly to scroll position.
动画进度与滚动位置直接绑定。
Decide before this motion ships
Purpose, frequency, trigger, timing, interruptibility, and gesture rules shape the implementation.
Bind visual progress continuously to a scroll range to explain position, phases, or narrative progress.
Reserve for low-frequency featured narratives and data explanation; keep ordinary reading static.
Drive continuously from scroll position with explicit start, end, and stable out-of-range states.
Hold the first state before the range and the last after it, replaying exactly during reverse scroll.
Scroll input is naturally reversible, with rendering tied directly to current progress.
Preserve native scrolling and touch momentum, with keyboard, scrollbar drag, and assistive technology reaching the same result.
Freeze on the most informative static frame and provide textual progress or explanation.
Copy into your interface
Current parameters update visual-state CSS, semantic HTML, the required vanilla JS runtime, and an interaction prompt.
.motion-demo.motion-demo--scroll-driven-animation {
position: relative;
display: grid;
place-items: center;
width: min(100%, 34rem);
min-height: 15rem;
padding: 2rem;
overflow: hidden;
color: #18181b;
background: #fafafa;
border: 1px solid #e4e4e7;
border-radius: 12px;
isolation: isolate;
}
.motion-demo.motion-demo--scroll-driven-animation *, .motion-demo.motion-demo--scroll-driven-animation *::before, .motion-demo.motion-demo--scroll-driven-animation *::after {
box-sizing: border-box;
}
.motion-demo.motion-demo--scroll-driven-animation .motion-surface {
position: relative;
display: grid;
gap: 0.65rem;
width: min(100%, 18rem);
padding: 1.25rem;
background: #ffffff;
border: 1px solid #e4e4e7;
border-radius: 8px;
box-shadow: 0 1px 2px rgb(0 0 0 / 6%), 0 10px 28px rgb(0 0 0 / 7%);
}
.motion-demo.motion-demo--scroll-driven-animation .motion-line {
display: block;
width: 100%;
height: 0.5rem;
background: #e4e4e7;
border-radius: 999px;
}
.motion-demo.motion-demo--scroll-driven-animation .motion-line:last-child { width: 62%; }
.motion-demo.motion-demo--scroll-driven-animation .motion-button {
position: relative;
min-height: 2.75rem;
padding: 0.75rem 1.1rem;
color: white;
font: inherit;
font-weight: 700;
background: #18181b;
border: 0;
border-radius: 7px;
cursor: pointer;
}
.motion-demo.motion-demo--scroll-driven-animation .motion-button:focus-visible { outline: 3px solid rgb(37 99 235 / 32%); outline-offset: 3px; }
.motion-demo.motion-demo--scroll-driven-animation .motion-action-row {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-top: 0.85rem;
}
.motion-demo.motion-demo--scroll-driven-animation .motion-replay-scene {
width: min(100%, 22rem);
display: grid;
place-items: center;
}
.motion-demo.motion-demo--scroll-driven-animation .motion-pausable-scene {
width: min(100%, 22rem);
display: grid;
place-items: center;
}
.motion-demo.motion-demo--scroll-driven-animation button.motion-surface {
color: inherit;
font: inherit;
text-align: start;
cursor: pointer;
}
.motion-demo.motion-demo--scroll-driven-animation .motion-replay-button,
.motion-demo.motion-demo--scroll-driven-animation .motion-undo-button,
.motion-demo.motion-demo--scroll-driven-animation .motion-dismiss-button,
.motion-demo.motion-demo--scroll-driven-animation .motion-pause-button {
min-height: 2.75rem;
padding: 0.65rem 0.9rem;
color: #18181b;
font: 650 0.78rem/1 ui-sans-serif, system-ui, sans-serif;
background: #ffffff;
border: 1px solid #d4d4d8;
border-radius: 7px;
cursor: pointer;
}
.motion-demo.motion-demo--scroll-driven-animation .motion-replay-button:focus-visible,
.motion-demo.motion-demo--scroll-driven-animation .motion-undo-button:focus-visible,
.motion-demo.motion-demo--scroll-driven-animation .motion-dismiss-button:focus-visible,
.motion-demo.motion-demo--scroll-driven-animation .motion-pause-button:focus-visible,
.motion-demo.motion-demo--scroll-driven-animation [data-reorder-item]:focus-visible,
.motion-demo.motion-demo--scroll-driven-animation [data-swipe-target]:focus-visible {
outline: 3px solid rgb(37 99 235 / 32%);
outline-offset: 3px;
}
.motion-demo.motion-demo--scroll-driven-animation .motion-status {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.motion-demo.motion-demo--scroll-driven-animation .motion-ripple-ink {
position: absolute;
width: 12px;
height: 12px;
pointer-events: none;
background: rgb(255 255 255 / 72%);
border-radius: 50%;
transform: translate(-50%, -50%) scale(0.95);
opacity: 0;
}
.motion-demo.motion-demo--scroll-driven-animation .motion-state-stack { position: relative; width: min(100%, 18rem); height: 9rem; }
.motion-demo.motion-demo--scroll-driven-animation .motion-state { position: absolute; inset: 0; display: grid; place-items: center; padding: 1rem; background: #ffffff; border: 1px solid #e4e4e7; border-radius: 8px; }
.motion-demo.motion-demo--scroll-driven-animation .motion-state--to { background: #18181b; color: white; }
.motion-demo.motion-demo--scroll-driven-animation .motion-scroll-track > span { color: #71717a; font: 600 0.72rem/1.2 ui-monospace, monospace; letter-spacing: 0.08em; text-transform: uppercase; }
.motion-demo.motion-demo--scroll-driven-animation .motion-orbit-item { display: grid; place-items: center; width: 2.5rem; height: 2.5rem; color: white; background: #2563eb; border-radius: 50%; }
.motion-demo.motion-demo--scroll-driven-animation .motion-text-stack { position: relative; width: min(100%, 18rem); min-height: 4rem; }
.motion-demo.motion-demo--scroll-driven-animation svg { width: min(100%, 20rem); height: auto; }
.motion-demo.motion-demo--scroll-driven-animation { max-height: 15rem; overflow-y: auto; place-items: start center; }
.motion-demo.motion-demo--scroll-driven-animation .motion-scroll-track { display: grid; place-items: center; gap: 10rem; width: 100%; min-height: 48rem; padding-block: 5rem; }
.motion-demo.motion-demo--scroll-driven-animation .motion-surface { animation: motion-scroll-driven-animation linear both; animation-timeline: scroll(nearest); animation-range: 10% 80%; }
@keyframes motion-scroll-driven-animation { from { transform: translateY(80px); } to { transform: none; } }
@supports not (animation-timeline: scroll()) { .motion-demo.motion-demo--scroll-driven-animation .motion-surface { animation: motion-scroll-driven-animation 700ms linear both; } }
@keyframes motion-scroll-driven-animation-reduced-fade {
from { opacity: 0.84; }
to { opacity: 1; }
}
.force-reduced-motion .motion-demo.motion-demo--scroll-driven-animation {
animation: motion-scroll-driven-animation-reduced-fade 160ms cubic-bezier(0.23, 1, 0.32, 1) both !important;
}
.force-reduced-motion .motion-demo.motion-demo--scroll-driven-animation *,
.force-reduced-motion .motion-demo.motion-demo--scroll-driven-animation *::before,
.force-reduced-motion .motion-demo.motion-demo--scroll-driven-animation *::after {
animation: none !important;
animation-delay: 0ms !important;
animation-iteration-count: 1 !important;
transition-property: opacity, color, background-color, border-color !important;
transition-duration: 160ms !important;
transition-delay: 0ms !important;
scroll-behavior: auto !important;
}
.force-reduced-motion .motion-demo.motion-demo--scroll-driven-animation .motion-button,
.force-reduced-motion .motion-demo.motion-demo--scroll-driven-animation [data-reorder-item],
.force-reduced-motion .motion-demo.motion-demo--scroll-driven-animation [data-swipe-target],
.force-reduced-motion .motion-demo.motion-demo--scroll-driven-animation [data-spring-target] {
transform: none !important;
}
.force-reduced-motion .motion-demo.motion-demo--scroll-driven-animation .motion-path { stroke-dashoffset: 0 !important; }
.force-reduced-motion .motion-demo.motion-demo--scroll-driven-animation .motion-word--from { opacity: 0 !important; visibility: hidden; }
.force-reduced-motion .motion-demo.motion-demo--scroll-driven-animation .motion-word:not(.motion-word--from) { opacity: 1 !important; filter: none !important; transform: none !important; }
@media (prefers-reduced-motion: reduce) {
.motion-demo.motion-demo--scroll-driven-animation {
animation: motion-scroll-driven-animation-reduced-fade 160ms cubic-bezier(0.23, 1, 0.32, 1) both !important;
}
.motion-demo.motion-demo--scroll-driven-animation *, .motion-demo.motion-demo--scroll-driven-animation *::before, .motion-demo.motion-demo--scroll-driven-animation *::after {
animation: none !important;
animation-delay: 0ms !important;
animation-iteration-count: 1 !important;
transition-property: opacity, color, background-color, border-color !important;
transition-duration: 160ms !important;
transition-delay: 0ms !important;
scroll-behavior: auto !important;
}
.motion-demo.motion-demo--scroll-driven-animation .motion-button,
.motion-demo.motion-demo--scroll-driven-animation [data-reorder-item],
.motion-demo.motion-demo--scroll-driven-animation [data-swipe-target],
.motion-demo.motion-demo--scroll-driven-animation [data-spring-target] {
transform: none !important;
}
.motion-demo.motion-demo--scroll-driven-animation .motion-path { stroke-dashoffset: 0 !important; }
.motion-demo.motion-demo--scroll-driven-animation .motion-word--from { opacity: 0 !important; visibility: hidden; }
.motion-demo.motion-demo--scroll-driven-animation .motion-word:not(.motion-word--from) { opacity: 1 !important; filter: none !important; transform: none !important; }
}Pre-delivery review
- Test the scroll range across viewport heights.
- Avoid per-frame layout work and large paint regions.
Keep information accessible
Freeze on the most informative static frame and provide textual progress or explanation.
Parameter reference
| Parameter | Range / options | Default | Purpose |
|---|---|---|---|
start | 0%–90% | 10% | Adjusts start. |
end | 10%–100% | 80% | Adjusts end. |
distance | 0px–240px | 80px | Controls how far the element travels. |
axis | Vertical / Horizontal | Vertical | Selects axis. |