I created my first scroll-driven animation in CSS

Posted 15 hours ago

I created my first scroll-driven animation using the CSS animation API this week! And it was so easy to create.

Last month, I started watching Bramus’ course on scroll-driven animation, Unleash the power of Scroll Animations. I applied what I was learning by revisiting some scroll-driven animation Codepens that I created with GreenSock (GSAP) (Codepen Collection). I had originally created these prototypes to get some practice because I not build out a project with scroll-driven animations before. I have only built site with scroll-triggered animations in the past.

Recently, I had flirted with the idea of using scroll-driven animations with a project at work. I thought the nature of scroll-driven animations lent itself well to the story-driven nature of the project (an annual report). And I thought it would give me a great opportunity to develop my skills by building out a real project (Just Build Websites). But after some further evaluation, I decided not pursue the scroll-driven narrative.

One of the pieces of this project was a timeline about different ministry projects that are part of a five year initiative. The timeline was one of the last things I built and I decided to go with a horizontal scroll to view the timeline elements. But one of the constraints that I butt up against was that macOS does not show horizontal scrollbars until you start scrolling (I could not find a way to force it with CSS). So there is not an affordance that indicated to the user that they could scroll the element to see more content. I had added a generated content element to fade the content on the right but it still did not seem to give a good affordance.

So I decided to try a scroll-driven animation as a progressive enhancement. It just so happened that the next lesson in Bramus’ course addressed this use case. I watched the video and then studied the demo he created for the lesson.

I only had to add one more element to my markup than I originally had written. I already had a containing div with a unordered list (ul) inside of it. I just had to add a “pin wrapper” div around the unordered list.

<div class="wow-timeline">
  <div class="pin-wrap-sticky">
    <ul class="pin-wrap">
	...
    </ul>
  </div>
</div>

Because this solution is a progressive enhancement, I put my code for the scroll-driven timeline solution inside a feature query (@supports). The .wow-timeline gets a height of 500vh because it is the scroll of this element that determines the timing of the animation. A higher value would mean a slower scroll animation and lower value, a faster one. I set up a view-timeline by adding a view-timeline-name. A view timeline name is always preceded by two hyphens (like a CSS custom property).

@supports (animation-timeline:scroll()) {
    .wow-timeline {
        display: flex;
        height: 500vh;
        overflow: visible;
        view-timeline-name: --section-pin-timeline;
        view-timeline-axis: block;
    }
}

Next you add add CSS for the pin-wrapper. It will be a sticky element so that it looks like your animation is “pinned” to the viewport. I made it a flex element so that I could vertically center my timeline in the viewport. The height is 88px less than 100% to account for a fixed navigation element at the top of the page.

@supports (animation-timeline:scroll()) {
    .wow-timeline .pin-wrap-sticky {
        display: flex;
        align-items: center;
        height: calc(100vh - 88px);
        position: sticky;
        top: 120px;
        width: 100vw;
        overflow-x: hidden;
    }
}

And finally I added CSS to my unordered list. The example from Bramus’ course had a set width. The unordered list is using CSS grid and by setting fit-content, the scroll-driven animation will work across responsive viewports.

@supports (animation-timeline:scroll()) {
    .wow-timeline .pin-wrap {
        padding-block-end: 0;
        width: fit-content;
        will-change: transform;
        animation: linear forwards move;
        animation-timeline: --section-pin-timeline;
        animation-range: contain 0% contain 100%;
    }
}
@keyframes move {
    100% {
        transform: translateX(calc(-100% + 100vw));
    }
}

Very simple to do. In less than 10 minutes, I had a scroll-driven animation that I progressively enhanced the page with. I highly suggest watching Bramus’s course. It will give you the basics you need to start creating your own scroll-driven animations.

Progressively enhanced scroll-driven timeline:

Default horizontal scroll solution:

Comment on this post