Enhance Your Website with Scroll Animations Using Pure CSS, No JavaScript Required

Adel Benyahia
7 min readMay 6, 2023

In this beginner-friendly tutorial, we will explore a new experimental scroll-driven animations API that allows us to easily add engaging animations to our website. The best part? We’ll achieve this using pure CSS, without the need for any additional JavaScript code. So, even if you have little experience with coding, you can create stunning scroll animations that will enhance your website’s visual appeal.

Photo by Marc-Olivier Jodoin on Unsplash

Table of contents

  • What is Scroll-driven animations?
  • How to enable the Scroll-driven animations API?
  • Scroll-driven animations API explained
  • Before start coding
  • Let’s code
  • Full project source code
  • Conclusion

What is Scroll-driven animations?

Scroll-driven animations are a common UX pattern on the web, generally used to position an element depending on a scroll container.

In addition to parallax background images and reading indicators, scroll-driven animations can be used to create a variety of interactive and engaging effects on a website. For instance, scrolling can trigger the appearance of text or images, the animation of icons or graphics, or the transformation of an element’s size, shape, or color.

Scroll-driven animations can also be used to enhance the storytelling aspect of a website, by creating a sense of progression and anticipation as the user scrolls through the content. For instance, a scroll-driven animation can be used to reveal a key message or call-to-action in a subtle and impactful way.

Moreover, scroll-driven animations can improve the overall user experience by providing visual cues and feedback that guide the user’s attention and navigation. By animating elements in response to the user’s scrolling behavior, scroll-driven animations can make the website feel more interactive, dynamic, and intuitive.

How to enable the Scroll-driven animations API?

If you visit the CanIUse website (link), you can see that this API under active development and still in the early stage of adoption, it can be enabled in Chrome via the #enable-experimental-web-platform-features flag in chrome://flags

Scroll-driven animations API explained

This API comes with a new concept that work in conjunction with the existing Web Animations API (WAAPI) and CSS Animations API to enable declarative scroll-driven animations using only pure CSS.

Integrating scroll-driven animations with these built-in APIs, make it possible to have these animations run off the main thread, With that you can now have smooth animations and all of that with a few CSS lines of code.

In the Scroll-driven animations API, we have two types of animation:

  • Scroll Progress Timeline: a timeline that is linked to the scroll position of a scroll container along a particular axis.
  • View Progress Timeline: a timeline that is linked to the relative position of a particular element within its scroll container.

Before Start coding

We have talked about web animation and on scroll animations before, and we have exploring build in API that lets us do all kind of web animations just with native API and a little of JavaScript.

But what make me exited about this new (experimental) API, that it uses only CSS to manage that, no extra JavaScript.

Yes, with a CSS key frame animation only, you can do all these tricks that need a lot of work and extra packages.

Let’s code

Scroll Progress Timeline

A Scroll Progress Timeline is an animation timeline that is linked to progress in the scroll position of a scroll container, along a particular axis (horizontal or vertical axis). It converts a position in a scroll range into a percentage of progress.

The animation start from 0% (in the beginning of the scrolling) to 100% at the end of scrolling.

The easiest way to create a Scroll Timeline in CSS is to use the scroll() function. This creates an anonymous Scroll Timeline that you can set as the value for the new animation-timeline property.

@keyframes animate-it {
.element {
animation: animate-it linear;
animation-timeline: scroll(root block);

The scroll() function accepts two parameters:<scroller> and <axis>.
For the <scroller> argument, we have three accepted values:
— nearest: Uses the nearest ancestor scroll container (default).
— root: Uses the document viewport as the scroll container.
— self: Uses the element itself as the scroll container.

For the <axis> argument, we have these values:
— block: Uses the measure of progress along the block axis of the scroll container (default).
— inline: Uses the measure of progress along the inline axis of the scroll container.
— y: Uses the measure of progress along the y-axis of the scroll container.
— x: Uses the measure of progress along the x-axis of the scroll container.

This is a strike forward example:

<div id="progress"></div>

For the CSS part:

@keyframes scroll-progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }

#progress__bar {
position: fixed;
left: 0; top: 0;
width: 100%; height: 10px;
background: red;
transform-origin: 0 50%;
animation: scroll-progress auto linear;
animation-timeline: scroll();

We have a simple keyframe CSS animation: that start from the position 0 to position 1 (full width) all of that goes horizontally.

And a simple CSS styling to put the progress bar at the Top of the page, with a red color.

View Progress Timeline

This Timeline animation is compared to IntersectionObserver, the animation start only when the element enters to the viewport and ended when it exit the viewport.

This is useful especially for example for section animations and parallax background effect.

To create a View Progress Timeline, we will use the view() function. with two arguments: <axis> and <view-timeline-inset>.

The <axis> is the same as from the Scroll Progress Timeline.
The <view-timeline-inset> will have an offset (positive or negative) to adjust the limits when an element is considered to be in or out of the view.

For example, to animate an element when it enters the view block, add the “view (block)” to the CSS animation-timeline property and set the animation-duration to “auto”.

And because “block” and “auto” are the default values for each of these CSS properties, we will simply omit them.

@keyframes reveal {
from { opacity: 0; }
to { opacity: 1; }

img {
animation: reveal linear;
animation-timeline: view();

By default, the View Progress Timeline animation from the moment the element is about to enter the scrollport and ends when the subject has left the scrollport entirely.

We can easily change this behavior by targeting different ranges in our animation:

  • cover: Represents the full range of the view progress timeline.
  • entry: Represents the range during which the principal box is entering the view progress visibility range.
  • exit: Represents the range during which the principal box is exiting the view progress visibility range.
  • entry-crossing: Represents the range during which the principal box crosses the end border edge.
  • exit-crossing: Represents the range during which the principal box crosses the start border edge.
  • contain: Represents the range during which the principal box is either fully contained by, or fully covers, its view progress visibility range within the scrollport. This depends on whether the subject is taller or shorter than the scroller.

You can take a look at this webpage to see these ranges in action:

Now let’s see this example:

@keyframes slide-from-left {
from {
opacity: 0;
transform: translateX(-100%);
filter: blur(5px);
to {
opacity: 1;
transform: translateX(0);
filter: blur(0);
.animate-card {
animation: auto linear slide-from-left both;
animation-timeline: view();
animation-range: entry 25% cover 50%;

We have an animation keyframes that start with a hidden (opacity 0) then the element slide from the left on the x-axis (-100%) to the right (0) ending by an opacity of (1), with a blue effect.

The animation is applied in the CSS class: animate-card

animation: auto linear slide-from-left both;

The animation is executed automatically with the “slide-from-left” effect in the both senses (left and right in this case)

animation-timeline: view();

The View animation is linked to the port view with the default value (view(block))

animation-range: entry 25% cover 50%;

The animation will be executed in the range of: 25% of the entry range and 50% of the cover range.

Let’s see the code in action

Full project source code


Us you can see, the web API are coming more and more powerful, and even can totally replace some major third party libraries, with just few CSS lines we are capable to generate different animations that before takes a lot of work, all of that with native APIs and with a little of memory uses.

This API is in the early stage of development and can change in the future, but I'm really excited to use this future in my next projects.



Adel Benyahia

Web application developer (HTML │ CSS │ JS | ReactJS | NextJS | NestJS | MERN)