Scroll-driven animation

Bind animation progress to a scroll range and tune its start, end, and travel.

Preview

Live preview

An animation whose progress is tied directly to scroll position.

Preview
Scroll
Scroll-driven animation
Continue
Authoritative vocabulary

Definition and close-term distinctions

English definitions are preserved verbatim from Animation Vocabulary; translations and distinctions help you choose precisely.

Canonical termscroll-driven-animation

Scroll-driven animation

滚动驱动动画
Authoritative definition

An animation whose progress is tied directly to scroll position.

Chinese translation

动画进度与滚动位置直接绑定。

Browse all 91 terms
Design contract

Decide before this motion ships

Purpose, frequency, trigger, timing, interruptibility, and gesture rules shape the implementation.

Purpose

Bind visual progress continuously to a scroll range to explain position, phases, or narrative progress.

Frequency

Reserve for low-frequency featured narratives and data explanation; keep ordinary reading static.

Trigger

Drive continuously from scroll position with explicit start, end, and stable out-of-range states.

Enter / exit

Hold the first state before the range and the last after it, replaying exactly during reverse scroll.

Interruptibility

Scroll input is naturally reversible, with rendering tied directly to current progress.

Gesture and keyboard

Preserve native scrolling and touch momentum, with keyboard, scrollbar drag, and assistive technology reaching the same result.

Reduced-motion strategy

Freeze on the most informative static frame and provide textual progress or explanation.

Output

Copy into your interface

Current parameters update visual-state CSS, semantic HTML, the required vanilla JS runtime, and an interaction prompt.

Updates with parameters
motion.css
.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; }
}
Review notes

Pre-delivery review

  • Test the scroll range across viewport heights.
  • Avoid per-frame layout work and large paint regions.
Accessibility

Keep information accessible

Reduced motion

Freeze on the most informative static frame and provide textual progress or explanation.

Parameters

Parameter reference

ParameterRange / optionsDefaultPurpose
start0%–90%10%Adjusts start.
end10%–100%80%Adjusts end.
distance0px–240px80pxControls how far the element travels.
axisVertical / HorizontalVerticalSelects axis.