Photo by Marc Sendra Martorell on Unsplash
Enhance Your Website with Scroll Animations Using Pure CSS, No JavaScript Required
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.
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
https://chromestatus.com/feature/6454455685873664
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.
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:
<body>
<div id="progress"></div>
...
</body>
@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:
https://scroll-driven-animations.style/tools/view-timeline/ranges/
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
https://codesandbox.io/p/sandbox/intersection-observer-api-example-y4riim
Conclusion
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.