Robin Marillia
Back to home

Swippable carousel with the scroll timeline API

This article was written in August 2023 with a previous version of the polyfill. The APIs should not have changed a lot but keep in mind that it is not up to date.

Using the future scroll timeline API, we will create a swippable carousel component that should be usable with any framework.

Interactive example (see in mobile)

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

Interactive example (see in mobile)

Tada ! We have a fully functional swippable carousel with a smooth indicator animation, soon to be usable in all browsers without javascript !