Using the future scroll timeline API, we will create a swippable carousel component that should be usable with any framework.
Modern CSS is cool
The future scroll timeline API is a new API that allows us to create scroll-based animations. It is currently only supported in Chrome beta, but it is usable now via this up-to-date polyfill
There are two APIs, the CSS API and the JS API. The polyfill aims to support both but there are still some issues that will prevent us to create our carousel without a little of javascript.
Once this css feature ships to every browsers, combined with the already existing scroll snapping properties, we will be able to create a fully functional swippable carousel without any JS.
The basic sliding mechanism
The base for our carousel will be a section containing an html list of items, and a div containing corresponding dots indicators:
<section class="carousel">
<ul class="slides-container">
<li class="slide">slide 1</li>
<li class="slide">slide 2</li>
<li class="slide">slide 3</li>
</ul>
<div class="dots-container">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</section>
For the css, the magic lies on the overflow-x, scroll-snap-type and scroll-snap-align css properties.
.carousel {
display: flex;
flex-direction: column;
gap: 16px;
max-width: 300px;
}
.slides-container {
overflow-x: auto;
scroll-snap-type: x mandatory;
display: flex;
gap: 16px;
}
.slide {
min-width: 300px;
height: 300px;
scroll-snap-align: start;
list-style: none;
background: #ededed;
border-radius: 16px;
display: grid;
place-content: center;
}
.dots-container {
display: flex;
justify-content: center;
gap: 8px;
}
.dot {
width: 12px;
height: 12px;
border-radius: 9999px;
background-color: cyan;
}
And that is all for the sliding behaviors ! Now let’s see how we can add the current sliding indicator by coloring the corresponding dots.
The ViewTimeline magic
Animate slides on scroll
First let’s see how we can play with the view timelines to animates our slides on horizontal scroll. Start by adding the following line to the .slide css class:
{
view-timeline: --carousel x;
}
This will create a “view” timeline named --carousel following the x axis. Then we tell our slides to use the —carousel timeline for their animations by adding this line:
{
animation-timeline: --carousel;
}
Finally we can use any animations we want on our slides, for example here with fade-in-out-animation which fade from opacity 0 to 1 and back to 0:
{
animation: linear fade-in-out-animation;
}
It give us the following final slide class:
.slide {
min-width: 300px;
height: 300px;
scroll-snap-align: start;
list-style: none;
background: #ededed;
border-radius: 16px;
display: grid;
place-content: center;
view-timeline: --carousel x;
animation-timeline: --carousel;
animation: linear fade-in-out-animation;
}
Unfortunately, for now, the polyfill prevents us to animate other elements than the element on which we set the view-timeline property (we need the timeline-scope property which is not currently supported in the polyfill, see https://github.com/flackr/scroll-timeline/issues/123).
So we will have to use a little bit of javascript to animate our dots.
Animate dots on scroll
We will use data attributes to select our elements as it is a good way to select things without having to rely on classes or ids, more often used for styling rather than logic (it would allow us to switch to Tailwindcss without having to change our javascript for example).
<section class="carousel">
<ul class="slides-container" data-slides-container>
<li class="slide">slide 1</li>
<li class="slide">slide 2</li>
<li class="slide">slide 3</li>
</ul>
<div class="dots-container" data-dots-container>
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</section>
const keyframes = [
{
scale: 1,
backgroundColor: "#e2e8f0",
},
{
scale: 1.3,
backgroundColor: "#f472b6",
},
{
scale: 1,
backgroundColor: "#e2e8f0",
},
];
const slidesContainer = document.querySelector("[data-slides-container]");
const dotsContainer = document.querySelector("[data-dots-container]");
[...dotsContainer.children].forEach((dot, index) => {
dot.animate(keyframes, {
fill: "both",
timeline: new ViewTimeline({
subject: slidesContainer.children[index],
axis: "inline",
}),
rangeStart: "cover",
rangeEnd: "cover",
});
});
Here, the only thing that differs from a normal animation is the timeline property. We create a new ViewTimeline and set the subject property to the corresponding slide.
This will allow us to animate the dots when the corresponding slide is arrives in the view.
We set the rangeStart and rangeEnd properties to cover to make sure the animation is played directly when the slide comes into the view.
By setting it to contain it would play the animation only when the slide is fully in the view.
The results
Tada ! We have a fully functional swippable carousel with a smooth indicator animation, soon to be usable in all browsers without javascript !