Scroll-driven animations link the progress of a CSS animation to the scroll position or to the visibility of an element. They run off the main thread, so they are smoother than JavaScript scroll listeners and need no IntersectionObserver.

Reveal an element as it enters the screen

css">@keyframes lotgrafix-reveal {
    from {
        opacity: 0;
        transform: translateY( 40px );
    }
    to {
        opacity: 1;
        transform: translateY( 0 );
    }
}

@supports ( animation-timeline: view() ) {
    .lotgrafix-reveal {
        animation: lotgrafix-reveal linear both;
        animation-timeline: view();
        animation-range: entry 0% cover 30%;
    }
}

Reading progress bar

<div class="lotgrafix-progress"></div>
@keyframes lotgrafix-grow {
    from { transform: scaleX( 0 ); }
    to   { transform: scaleX( 1 ); }
}

.lotgrafix-progress {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 4px;
    background: orangered;
    transform-origin: 0 50%;
    animation: lotgrafix-grow linear both;
    animation-timeline: scroll( root );
}

Key properties

  • animation-timeline: scroll(): progress follows the scroll position of a container.
  • animation-timeline: view(): progress follows how much the element is visible in the viewport.
  • animation-range: defines which part of the scroll or visibility range drives the animation, for example entry, exit or cover.

Always respect reduced motion

@media ( prefers-reduced-motion: reduce ) {
    .lotgrafix-reveal,
    .lotgrafix-progress {
        animation: none;
    }
}

Support note

Use @supports as shown above. Browsers without support skip the animation and show the normal final state, so content is never hidden.

«
»