First published August 23, 2024

Animate an Auto-Scrolling Carousel with Only HTML and CSS

No libraries, no JavaScript, just CSS keyframes and the scroll snap module.

Cellular-connected electronic kiosk demo app showing weather forecast in Barcelona

Introduction

As web developers working with powerful computers we don't often stop to consider that while things like memory and stable Internet connections aren't a problem for us, they may be where our apps end up running.

Case in point: building a web app to run on a Raspberry Pi - which could be quite possible for something like a kiosk in a mall, a doctor's office or even an amusement park.

Last year, I was tasked with building a small demo for work to demonstrate how to package up a simple web application and download it to a Raspberry Pi using a cellular-based Blues Notecard.

If you're interested in how to do this, you can read the full details here. But that is outside the scope of this post.

I decided a good demo would be a weather app where a list of cities would be provided, and the app would fetch the weather forecast for each city and display it in an endlessly looping carousel.

In order to keep the app size small (and quicker to download via cellular data to a Raspi), I built it with pure HTML, CSS, and a little vanilla-JavaScript.

For the auto-scrolling feature of each city's forecast I'd normally look for a carousel library to help me, but since I needed the app to stay lightweight I did some research and found that CSS alone could do what I needed.

Two Englishmen saying 'Huzzah'

Today, I'll show you how to use CSS keyframes animations along with scroll snap module and a few well-placed CSS classes to make auto-scrolling, CSS-only carousels possible.

Here's what the final auto-scrolling carousel animation looks like.

CSS keyframes and scroll snap

Before we get to the implementation of this auto-scrolling carousel,let's talk a bit about the CSS features that make it possible: CSS keyframes and the CSS scroll snap module.

CSS keyframes

The @keyframes rule controls the intermediate steps in a CSS animation sequence by defining styles for keyframes (or waypoints) along an animation sequence. This gives more control over the intermediate steps of the animation sequence than CSS transitions.

Not only do @keyframes allow for complex animations without the need for JavaScript, but they also offer a straightforward syntax, and reusability across multiple elements (as you'll soon see in my own code examples below).

CSS scroll snap

The CSS scroll snap module provides properties that let you control the panning and scrolling behavior by defining snap positions. Content can be snapped into position as the user scrolls overflowing content within a scroll container, providing paging and scroll positioning.

It has two properties in particular we'll take advantage of today for the carousel.

CSS scroll-snap-type

The CSS scroll-snap-type property sets how strictly snap points are enforced on the scroll container.

It accepts a variety of different values, but the most common ones are:

  • mandatory - The scroll container must snap to the nearest snap point after scrolling.
  • proximity - The scroll container will only snap to a point if the scrolling comes to a natural rest near a snap point.
  • none - No snapping behavior is enforced.

In practice, you might use mandatory when you need precise control over the final position of the scroll (like with carousels or paginated content where each item should be fully visible after scrolling), and you might use proximity when you want to give users more control over the scroll stopping point, which can feel smoother and less jarring.

CSS scroll-snap-align

The other part of this scroll snap equation, scroll-snap-align, specifies a box's snap position within its snap container when using the CSS scroll snap module.

A child element of the scroll snap container can have the scroll-snap-align property set to control how it snaps into the viewport when scrolling stops. Its values include:

  • start - Aligns the start edge of the item with the start edge of the scroll container.
  • end - Aligns the end edge of the item with the end edge of the scroll container.
  • center - Centers the item in the scroll container.
  • none - No snap alignment will occur.

With that bit of background out of the way, let's get on to the code.

Set up the HTML and CSS

First, the proper HTML must be set up for the CSS auto-scrolling to work correctly.

1. Wrap the HTML elements to be part of the carousel in a parent div.

To begin, wrap all the slides to be scrolled through inside a parent container. This is where the CSS scroll-snap-type property will be applied so that the child elements will be able to interpret the scroll-snap-align property applied to them.

NOTE: In my full code sample, I defined an HTML template to hold all my weather data for each city, and the template.content.cloneNode() function to clone the template for each city in my list and add it inside the weather-forecast-container element

That's outside the scope of what's needed for this article, but if you'd like to see the full code, it's available here.

Here is my HTML file where the parent container and its children are defined.

index.htm

  <div class="weather-forecast-container">
    <div class="weather-template">
      <div class="current-header-wrapper">
        <div class="current-date"></div>
        <h2 class="location">Loading current weather...</h2>
      </div>
      <div class="current-conditions">
        <div>
          <span class="conditions">
            <img class="condition-icon" src="" />
            <span class="condition-text" />
          </span>
        </div>
        <div class="current-temp"></div>
        <div class="details">
          <div class="wind-speed"></div>
          <div class="rainfall"></div>
          <div class="pressure"></div>
        </div>
      </div>
      <div class="forecast">
        <table>
          <tbody class="future-forecast"></tbody>
        </table>
      </div>
      <div class="weather-carousel-snapper"></div>
    </div>
  </div>  

As you can see from the HTML above, the weather-forecast-container class wraps the div with the class of weather-template which holds all the weather info for a given city, and contains an empty div at the bottom with a class of weather-carousel-snapper.

2. Position the empty div at the bottom of the weather-template div to be the carousel's snapper element.

Make sure that this element is inside of the weather-template div and not a sibling to it. It will continue to be empty, but it needs to be present.

<div class="weather-carousel-snapper"></div>

3. Add base styling to the parent container and position the weather template and scroll-snap div inside it.

This is where the CSS scroll snap module comes into play.

styles.css

.weather-forecast-container {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  height: clamp(290px, 88vh, 430px);
  max-width: 780px;
  margin: 8px auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  overflow-x: scroll;
}

Inside the weather-forecast-container class, we're going to go beyond the basic styling of the container like positioning and defining its height and width (both set to the width of a small screen attached to the Raspi), and add the first CSS scroll snap properties here.

Next up, is the styling for the weather-template div that contains the weather info and the empty weather-carousel-snapper div.

styles.css

.weather-template {
  position: relative;
  flex: 0 0 100%;
  background-size: 780px 100%;
  background-repeat: no-repeat;
  border: 1px solid rgba(0, 0, 0, 0.25);
  border-radius: 4px;
  padding: 0 10px;
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
}

I've included the stying for the weather-template div as it's the true parent of the weather-carousel-snapper element, but this is just standard CSS styling.

styles.css

.weather-carousel-snapper {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  scroll-snap-align: center;
}

Last but not least, we add the last bit of scroll snap CSS to the empty div with the class of weather-carousel-snapper.

4. Create the animation keyframes to auto-scroll the forecast slides

After the CSS styling is done, and scroll snap is enabled, it's time to make the @keyframes animations that will actually make the different cities appear to slide through the viewport from one city to the next.

styles.css

@keyframes tonext {
  75% {
    left: 0;
  }
  95% {
    left: 100%;
  }
  98% {
    left: 100%;
  }
  99% {
    left: 0;
  }
}

@keyframes tostart {
  75% {
    left: 0;
  }
  95% {
    left: -300%;
  }
  98% {
    left: -300%;
  }
  99% {
    left: 0;
  }
}

@keyframes snap {
  96% {
    scroll-snap-align: center;
  }
  97% {
    scroll-snap-align: none;
  }
  99% {
    scroll-snap-align: none;
  }
  100% {
    scroll-snap-align: center;
  }
}

In the code above, there are three separate animations defined.

  • tonext and tostart - manage the horizontal position of each city's weather forecast at different stages of the carousel, creating a sliding effect either to the right or far left, followed by a reset to the starting position (for getting the animation to endlessly loop back to the first city in the list again).
  • snap - controls the scroll-snap-align property, adjusting the snapping behavior at specific points in the animation timeline. This creates the dynamic snapping behavior that centers up the next city forecast in the carousel.

By combining these three keyframes, we'll have the slick-looking animation in the demo video where the carousel appears to smoothly slide from one forecast to the next.

5. Apply the keyframe animations to the appropriate HTML elements inside the weather-forecast-container parent div.

Now we're going to apply the @keyframes animations defined in the previous step.

styles.css

@media (hover: hover) {
  .weather-carousel-snapper {
    animation-name: tonext, snap;
    animation-timing-function: ease;
    animation-duration: 4s;
    animation-iteration-count: infinite;
  }

  .weather-template:last-child .weather-carousel-snapper {
    animation-name: tostart, snap;
  }
}
  • @media (hover: hover) - in an effort to ensure that the carousel animations and scroll snap are not applied to devices where hovering isn't possible, such as with most smartphones and tablets, I wrapped these animations inside of the hover media query.

The .weather-carousel-snapper class is given the tonext and snap animations to run simultaneously with animation-name: tonext, snap;, and it will ease over 4 seconds and loop infinitely.

A rule targeting the last child of the .weather-template element with the class of weather-carousel-snapper, applies a different animation combination (tostart and snap), to reset the carousel after the last city's weather forecast has been displayed onscreen to get the carousel back to it's starting position (and first city in the list).

6. And there you have it: a CSS only, auto-scrolling carousel with just HTML and CSS.

The finished product demoed once more in this video.

Conclusion

In certain situations, it's really crucial that web applications be as small as possible. For instance, when that app may be running on a low power or underpowered system (like a Raspberry Pi).

When I needed to build a small demo app that would download over cellular to a Raspi, I decided to do it with plain HTML, CSS, and a bit of vanilla-JS to keep the zip file size as small as possible - no JS frameworks like React or Svelte to help me out.

I wanted to build a weather app that would display the forecast for a list of cities and auto-rotate through the list before returning to the beginning of the list to start over. At first, I thought I might need a carousel library to make this happen, but it turns out CSS scroll snap and keyframe animations can do exactly the same thing, no JS needed. That's pretty darn sweet.

Check back in a few weeks — I’ll be writing more about JavaScript, React, IoT, or something else related to web development.

Thanks for reading. I hope if you need a carousel in the future (auto rotating or not) that you'll try implementing it yourself with CSS - I think you'll be pleasantly surprised how little code is needed to make it happen. Enjoy!

References & Further Resources

Want to be notified first when I publish new content? Subscribe to my newsletter.