CSS Scroll-Driven SVG Animation: view() vs scroll()
Use animation-timeline: view() when an SVG should progress as it enters or crosses its scrollport. Use scroll() when progress should follow a container or the whole page. Keep the finished SVG visible by default, enhance inside @supports, and disable motion for people who request it.
Choose view() or scroll() in 30 seconds
Use view() when the position of one SVG in its scrollport should control the animation. It is the right model for an illustration that draws, fades, or assembles while entering the viewport.
Use scroll() when the scroll position of a container should control the animation. It suits reading-progress graphics, diagrams tied to a long section, and effects that should track the whole page or a specific scrolling panel.
- Choose view() for “animate this SVG as it enters or crosses the visible area.”
- Choose scroll() for “map this scroller’s position from its start to its end.”
- Choose a named view timeline when an outer SVG should control several descendant paths or groups.
- Use animation-range to limit the useful portion of a view timeline.
- Use a time-based trigger or IntersectionObserver when the effect must play once instead of scrubbing backward with the page.
A scroll-driven animation is not the same as a scroll-triggered animation. A driven animation continuously maps scroll position to keyframe progress. If the visitor scrolls backward, the animation normally reverses. A triggered animation crosses a boundary and then starts an ordinary time-based animation. Read Scroll-Triggered SVG Animation: Where to Start when play-once behavior is the real requirement.
How CSS scroll-driven animation works
A normal CSS animation uses the document timeline: time advances, so the animation advances. The animation-timeline property lets another source provide progress. The CSS Scroll-Driven Animations specification defines two relevant sources:
- A view progress timeline measures where a subject’s box is within its nearest ancestor scroll container.
- A scroll progress timeline measures how far a scroll container has moved through its scrollable range.
The keyframes remain ordinary CSS. Only the timeline changes. That separation is useful: the same SVG animation idea can be controlled by time, the SVG’s visibility, or a scroller without rewriting its visual states.
The specification remains a working draft, and browser interoperability is still improving through Interop 2026. Treat the feature as progressive enhancement: render the meaningful final state first, then add scroll behavior only when the browser recognizes the timeline syntax.
Build a safe view() reveal for an entire SVG
The smallest production pattern applies an anonymous view timeline directly to the outer svg element. The SVG is both the subject being tracked and the element being animated.
Start with accessible, responsive SVG markup. The title and description explain the illustration when it carries meaning. If the artwork is decorative, use aria-hidden="true" instead of the image semantics shown here.
<svg
class="feature-diagram"
viewBox="0 0 320 180"
role="img"
aria-labelledby="feature-diagram-title feature-diagram-desc"
>
<title id="feature-diagram-title">Three connected workflow stages</title>
<desc id="feature-diagram-desc">
A line connects planning, animation, and testing.
</desc>
<path
class="feature-diagram__line"
d="M60 90 H260"
/>
<circle class="feature-diagram__node" cx="60" cy="90" r="22" />
<circle class="feature-diagram__node" cx="160" cy="90" r="22" />
<circle class="feature-diagram__node" cx="260" cy="90" r="22" />
</svg>The base rules show the completed illustration. Supported browsers replace that static state with a view-driven entrance. Notice that animation-timeline comes after the animation shorthand. The shorthand resets a previously declared timeline to its default value.
.feature-diagram {
display: block;
width: 100%;
height: auto;
opacity: 1;
transform: none;
}
.feature-diagram__line {
fill: none;
stroke: currentColor;
stroke-width: 4;
}
.feature-diagram__node {
fill: currentColor;
}
@supports (animation-timeline: view()) {
.feature-diagram {
animation: feature-diagram-arrival 1ms linear both;
animation-timeline: view(block);
animation-range: entry 10% cover 45%;
}
}
@keyframes feature-diagram-arrival {
from {
opacity: 0;
transform: translateY(2rem) scale(0.96);
}
to {
opacity: 1;
transform: none;
}
}
@media (prefers-reduced-motion: reduce), print {
.feature-diagram {
animation: none;
opacity: 1;
transform: none;
}
}view(block) tracks the SVG along the logical block axis of its nearest ancestor scroll container. Logical axes are preferable to hard-coded vertical or horizontal assumptions because they follow the page’s writing mode.
The range begins 10 percent into the subject’s entry and finishes at 45 percent of the broader cover range. The exact percentages are design choices, not universal defaults. Test them with short and tall viewports instead of tuning only on one desktop screen.
Draw SVG paths with a named view timeline
An anonymous view() timeline is convenient when the tracked subject and animated element are the same. A path-drawing sequence often needs a different relationship: the outer SVG should be tracked, while one or more descendant paths should animate.
Use a named view timeline for that structure. Timeline names begin with two hyphens. A timeline declared on an element is available to that element and its descendants, so a path inside the SVG can reference the outer SVG’s progress.
<svg
class="route-map"
viewBox="0 0 640 240"
role="img"
aria-labelledby="route-map-title route-map-desc"
>
<title id="route-map-title">Delivery route with three stops</title>
<desc id="route-map-desc">
A curved route travels from the warehouse through two stops to its destination.
</desc>
<path
class="route-map__guide"
d="M60 180 C170 40 300 40 390 130 S520 210 580 70"
/>
<path
class="route-map__route"
pathLength="1"
d="M60 180 C170 40 300 40 390 130 S520 210 580 70"
/>
<circle class="route-map__stop" cx="60" cy="180" r="10" />
<circle class="route-map__stop" cx="390" cy="130" r="10" />
<circle class="route-map__stop" cx="580" cy="70" r="10" />
</svg>The pathLength="1" attribute creates a convenient normalized authoring scale. A dash length and dash offset of 1 can represent the complete route without copying a measured pixel length into CSS. See How SVG Line Drawing Animation Works for a deeper explanation of the dash technique.
.route-map {
display: block;
width: 100%;
height: auto;
}
.route-map__guide,
.route-map__route {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
.route-map__guide {
stroke: #d8dee9;
stroke-width: 8;
}
.route-map__route {
stroke: #2563eb;
stroke-width: 8;
stroke-dasharray: 1;
stroke-dashoffset: 0;
}
.route-map__stop {
fill: #2563eb;
}
@supports (animation-timeline: view()) {
.route-map {
view-timeline: --route-map block;
}
.route-map__route {
stroke-dashoffset: 1;
animation: draw-route-map 1ms linear both;
animation-timeline: --route-map;
animation-range: entry 15% cover 55%;
}
}
@keyframes draw-route-map {
from {
stroke-dashoffset: 1;
}
to {
stroke-dashoffset: 0;
}
}
@media (prefers-reduced-motion: reduce), print {
.route-map__route {
animation: none;
stroke-dashoffset: 0;
}
}This pattern keeps the full route visible when scroll timelines are unavailable, motion is reduced, or the page is printed. It also avoids making the illustration’s meaning depend on a browser reaching the final keyframe.
If several paths need different timing, attach each animation to --route-map and give each one a distinct animation-range. Keep the sequence short enough that the relationship remains understandable when the visitor scrolls quickly or reverses direction.
Use scroll() for an SVG reading-progress line
The scroll() function tracks a scroller rather than the visibility of the animated subject. Its optional arguments select the source and axis. scroll(root block) means the document viewport’s scrolling mechanism on the logical block axis.
A reading-progress line is a good use because its visual state is directly proportional to page progress. It is supplementary information, so the example hides it when the feature is unsupported or reduced motion is requested.
<svg
class="reading-progress"
viewBox="0 0 100 2"
preserveAspectRatio="none"
aria-hidden="true"
focusable="false"
>
<path
class="reading-progress__track"
d="M0 1 H100"
pathLength="1"
/>
<path
class="reading-progress__value"
d="M0 1 H100"
pathLength="1"
/>
</svg>.reading-progress {
display: none;
position: fixed;
inset: 0 0 auto;
width: 100%;
height: 0.25rem;
z-index: 10;
}
.reading-progress__track,
.reading-progress__value {
fill: none;
stroke-width: 2;
}
.reading-progress__track {
stroke: #d8dee9;
}
.reading-progress__value {
stroke: #2563eb;
stroke-dasharray: 1;
stroke-dashoffset: 1;
}
@supports (animation-timeline: scroll()) {
.reading-progress {
display: block;
}
.reading-progress__value {
animation: update-reading-progress 1ms linear both;
animation-timeline: scroll(root block);
}
}
@keyframes update-reading-progress {
from {
stroke-dashoffset: 1;
}
to {
stroke-dashoffset: 0;
}
}
@media (prefers-reduced-motion: reduce), print {
.reading-progress {
display: none;
}
}Use scroll(nearest block) when the nearest scrolling ancestor should be the source, or a named scroll timeline when the source cannot be found reliably through that relationship. If the selected axis has no scrollable range, the timeline is inactive.
How animation-range controls view() timing
Without an attachment range, a view timeline spans the subject’s broader journey through its scrollport. animation-range lets the animation use a more meaningful portion of that journey.
- cover spans from the subject first entering until it completely leaves.
- entry covers the subject’s entry phase.
- contain represents the interval during which the subject is contained by the scrollport. Its behavior also accounts for subjects larger than the scrollport.
- exit covers the subject’s exit phase.
- entry-crossing and exit-crossing provide useful distinctions for subjects taller than their scrollports.
A value such as animation-range: entry 15% cover 55% sets a start point and an end point. It does not mean that the animation lasts for a percentage of time. Scroll distance supplies progress, so a visitor can move through the range slowly, quickly, or in reverse.
Use named ranges before reaching for arbitrary offsets. They describe intent and make the CSS easier to revisit. Then adjust percentages only after testing the actual illustration at representative viewport heights.
Progressive enhancement and browser support in 2026
Chrome shipped the core CSS scroll-driven animation feature in Chrome 115. Safari added scroll-driven animations in Safari 26.0, and Safari 26.4 moved them onto its threaded animation system. The feature is also an Interop 2026 focus because implementation details and cross-browser consistency still need work.
That combination calls for cautious adoption, not avoidance. The production rule is simple: the unsupported state must remain useful.
- Write base CSS that displays the final, meaningful SVG state.
- Add the timeline and its different starting state inside @supports.
- Remove nonessential motion for prefers-reduced-motion: reduce.
- Test real target browsers because @supports proves that syntax parses, not that every range and nesting case behaves identically.
- Use IntersectionObserver only when an unsupported browser must receive a comparable triggered effect.
Do not hide essential text, controls, instructions, or chart values until a scroll-driven animation completes. A visitor can arrive at an anchor midway through a page, use find-in-page, zoom the viewport, print the article, or navigate with assistive technology without reproducing your ideal scroll sequence.
Accessibility requirements for scroll-driven SVG
Respect reduced motion
A scroll timeline reacts directly to interaction, so it can create persistent motion while a person is navigating. Under prefers-reduced-motion: reduce, remove nonessential movement and show the settled state. Replacing a large translation with a smaller fade may be appropriate in some products, but the safest default for a decorative reveal is no animation.
Read How to Respect prefers-reduced-motion for a complete motion policy and testing workflow.
Preserve the information without the animation
An animation can explain order, direction, or change, but that information must also be available in text, labels, or a static visual state. The route example includes a concise title and description and remains fully drawn when motion is disabled.
For more guidance on choosing title, desc, accessible names, and decorative treatment, see Accessible SVG: title, desc, ARIA Labels, and Decorative Icons.
Avoid scroll hijacking
A scroll-driven animation should observe normal scrolling, not change it. Do not trap wheel, touch, or keyboard input to force the visitor through the scene. Keep page movement predictable and ensure controls remain usable at 200 percent zoom and with keyboard navigation.
Do not announce decorative progress
The reading-progress SVG uses aria-hidden="true" because announcing every visual progress update would add noise. If completion percentage is functionally important, provide a separate semantic status designed for assistive technology and update it at a sensible cadence.
Performance: CSS-only does not mean cost-free
Moving the relationship between scrolling and animation into CSS avoids a JavaScript scroll handler and gives the browser more control over scheduling. Safari 26.4, for example, runs scroll-driven animations through its threaded animation system. That does not make every animated SVG property equally inexpensive.
- Prefer transform and opacity for large groups and broad entrance effects.
- Keep stroke-drawing effects focused on a modest number of paths.
- Avoid combining many animated filters, masks, blurs, and large translucent layers in the same scene.
- Do not add will-change to every animated element. Use it only after measurement identifies a real benefit.
- Test on a representative mobile device, not only a fast desktop computer.
SVG line drawing can require painting as the dash changes. That may be completely reasonable for one route or logo and expensive for hundreds of detailed paths. Measure the actual scene with the browser’s performance tools and reduce complexity when frames miss their budget. The checklist in SVG Animation Performance Best Practices covers broader SVG optimization.
Testing checklist before publication
- Scroll forward and backward. Confirm that reversal is intentional and that intermediate states remain visually coherent.
- Test multiple viewport heights. A range tuned on a tall monitor may be too short on a landscape phone.
- Test nested scrollers. An element with overflow: auto can become the nearest scroll container and change what view() observes.
- Test reduced motion at operating-system level. Verify the finished SVG appears without a flash of its hidden starting state.
- Test an unsupported browser. The static illustration and all associated content must remain complete.
- Test zoom, keyboard navigation, and anchor links. The page must work when the visitor does not follow the expected continuous scroll path.
- Test printing. No meaningful SVG should remain transparent, translated, clipped, or partially drawn.
- Profile the real scene. Look for excessive painting, long frames, or effects that become unstable while the main thread is busy.
Debugging CSS scroll-driven SVG animation
The animation runs by time instead of scroll
Check declaration order. An animation shorthand written after animation-timeline resets the timeline to auto. Put the shorthand first and the timeline declaration afterward.
The animation does not run at all
Inspect the selected scroller and axis. A scroll progress timeline is inactive when its source has no scrollable overflow on that axis. A view timeline can also be inactive when its subject has no principal box. Verify that the element is rendered and that the expected container actually scrolls.
view() follows the wrong container
view() always uses the subject’s nearest ancestor scroll container. Look for an intervening wrapper with overflow: auto, scroll, or another value that creates a scroll container. If the desired relationship is more complex, define a named timeline deliberately.
A child path cannot find the named timeline
Timeline scope follows the element tree. A timeline declared by the outer SVG is available to its descendants. It is not automatically available to unrelated siblings or ancestors. Keep the animated paths inside the timeline-defining SVG, or use timeline-scope only when the wider relationship is genuinely required and has been tested across target browsers.
The SVG is invisible in an unsupported browser
The hidden or undrawn starting state was probably placed in the base CSS. Move that state into the @supports block. Base CSS should show the finished artwork.
The effect finishes too early or too late
Adjust animation-range, not a time duration. Scroll-driven progress is distance-based. Use the range vocabulary to select the correct phase, then refine its percentages while testing different viewport sizes.
The path draws in the wrong direction
The visible direction follows the path data. Reverse the path’s geometry in the design source or use a deliberate dash-offset direction. Keep the chosen method consistent across related paths so maintenance does not become guesswork.
Frequently asked questions
What is a CSS scroll-driven SVG animation?
It is a CSS keyframe animation whose progress comes from an element’s position in a scrollport or from a scroller’s current position, rather than elapsed time. The visual target can be an entire SVG, a group, or an individual SVG shape.
What is the difference between view() and scroll()?
view() tracks a subject as it moves through its nearest ancestor scrollport. scroll() tracks the scrolling progress of a selected container. Use the subject’s visibility for reveals and route drawings; use the container’s progress for page-wide or panel-wide indicators.
Does animation-timeline require JavaScript?
No. Keyframes, timelines, and attachment ranges can all be declared in CSS. JavaScript remains useful when business logic, persistent state, analytics, or a required fallback goes beyond a visual progressive enhancement.
Will a view() animation play only once?
Not by default. Its progress is tied to scroll position, so it can reverse when the visitor scrolls backward. If the requirement is “cross a boundary, then play a normal animation once,” use a scroll-triggered pattern instead.
Can one SVG timeline animate several paths?
Yes. Declare a named view timeline on the outer SVG and reference that name from descendant paths or groups. Each descendant can use a different attachment range while sharing the same tracked subject.
Is animation-timeline safe to use in production?
Yes, as a progressive enhancement with a complete static fallback. Support and interoperability are still uneven enough that essential information should never depend on the animation. Test target browsers and schedule a support review.
Does CSS scroll-driven animation automatically improve performance?
It removes the need for a JavaScript scroll handler for this mapping and gives the browser more scheduling control. Rendering cost still depends on the properties and SVG complexity being animated, so profiling remains necessary.
Should reduced-motion users see the final or initial state?
Usually the final state. Reduced motion should not remove information or leave a path half drawn. Disable the animation and explicitly restore the complete, meaningful SVG state.
Where to go next
Start with the whole-SVG view() pattern, verify its fallback and reduced-motion behavior, and only then move to named timelines for multi-part scenes. If the interaction needs timeline orchestration beyond CSS, compare the control models in CSS vs GSAP for SVG: A Decision Guide. For foundational CSS animation syntax, use Your First SVG Animation with CSS.
Official references
Sources and further reading
Related articles
SVG Animation Not Working? A Production Debugging Playbook
Fix broken SVG animations with a systematic checklist for embedding, CSS, JavaScript, paths, IDs, reduced motion, browser timing, and performance.