DEV Community

loading...

Making an infinite CSS carousel

wparad profile image Warren Parad Updated on ・4 min read

After searching and struggling for many hours, I still couldn't find a good guide on how to make an infinite CSS carousel. There's no reason this can't be done using html and css, and yet almost every guide is using javascript, or css that doesn't actually work when you try it.

It's so simple and yet a working example is just not available, so I'm sharing what we've done at Rhosys to make our brand carousel.

And of course you want to see the finished product first:
rhosys-brand-carousel

Diving into the code

Here's our simple display:

<section class="user-cloud">
  <div class="brands-container">
    <div class="brands-carousel">
      <picture>
        <source srcset="assets/images/s-customer-cloud1.png"
            media="(max-width: 766px)" />
        <img src="assets/images/customer-cloud1.png" />
      </picture>
      <picture>
        <source srcset="assets/images/s-customer-cloud2.png"
            media="(max-width: 766px)" />
        <img src="assets/images/customer-cloud2.png" />
      </picture>
      <picture>
        <source srcset="assets/images/s-customer-cloud3.png"
            media="(max-width: 766px)" />
        <img src="assets/images/customer-cloud3.png" />
      </picture>
      <picture>
        <source srcset="assets/images/s-customer-cloud4.png"
            media="(max-width: 766px)" />
        <img src="assets/images/s-customer-cloud4.png" />
      </picture>
    </div>
  </div>
</section>
Enter fullscreen mode Exit fullscreen mode

We have four images on mobile and only three on desktop. To make this responsive we are using the picture tag with source and img set. Img selectors didn't work, so we use picture. No idea why, but rather than fighting with that it's easier to do this. No messy x2 multiples or figuring out what the size of the elements should be on the screen.

Then we'll add some nice padding and setup to our container.

IMPORTANT: the max-width here should always been the same width as all your pictures, so that 100% means the full picture width:

.brands-container {
  /* all pictures should be the same size as this value. */
  max-width: 1050px;
  margin: auto;
  padding:0 1em;
  overflow: hidden;
}

.brands-carousel {
  position: relative;
  padding-left: 0;
  margin: 0;
  height: 200px;
  overflow: hidden;
}

.brands-carousel > div {
  width: 100%;
}
Enter fullscreen mode Exit fullscreen mode

That's the setup, which is relatively simple, and here's the important setup:

/* Each picture in the carousel is 100% of the parent. */
.brands-carousel > picture {
  width: 100%;
  position: absolute;
  top: 0;
  display: flex;
  justify-content: center;
  animation: carousel 20s linear infinite;

  /* It also starts off the screen until it is time. */
  transform: translateX(100%);
}
Enter fullscreen mode Exit fullscreen mode

(I'm going to skip talking about the first-picture keyframe for a second)
Every picture get's the same setup, it takes N seconds to move onto the screen and stay there until the next picture moves. Calculated on desktop there are three pictures, so each one gets 1/3 of the time on stage.

.brands-carousel > picture:nth-child(1) {
  animation-name: first-picture, carousel;
  animation-duration: 20s;
  animation-iteration-count: 1, infinite;
  animation-delay: 0s, 20s;
  transform: translateX(0%);
}
.brands-carousel > picture:nth-child(2) {
  animation-delay: Calc(20s * .33);
}
.brands-carousel > picture:nth-child(3) {
  animation-delay: Calc(20s * .66);
}
Enter fullscreen mode Exit fullscreen mode
/* The keyframes for desktop */
@keyframes first-picture {
  0% { transform: translateX(0%); }
  7.5%, 33% { transform: translateX(0); }
  40.5%, 100% { transform: translateX(-100%); }
}

@keyframes carousel {
  0% { transform: translateX(100%); }
  7.5%, 33% { transform: translateX(0); }
  40.5%, 100% { transform: translateX(-100%); }
}
Enter fullscreen mode Exit fullscreen mode

So the main keyframe is carousel. Since each image is on stage for 1/3 of the time, It will slide in taking 7.5% of the 20s time to do that, and stay there until the end of it's 33%, and which time it transfers out. Since each image takes 7.5% to enter, it also has to take 7.5% to leave.
33% + 7.5% = 40.5.

And this almost totally works, except for one thing, until the 33% of 20s no image is fully displayed. The fix for this is a hack which shows the first image two times. We'll show it on the screen to start until it leaves and at the same time we'll show it off the screen to the right until it starts. We'll then delay the second animation one full round. Because of this, we need the first-picture keyframe, and this works great.

/* The keyframes for mobile */
@keyframes first-picture-responsive {
  0% { transform: translateX(0%); }
  5.5%, 25% { transform: translateX(0); }
  30.5%, 100% { transform: translateX(-100%); }
}

@keyframes carousel-responsive {
  0% { transform: translateX(100%); }
  5.5%, 25% { transform: translateX(0); }
  30.5%, 100% { transform: translateX(-100%); }
}
Enter fullscreen mode Exit fullscreen mode

Don't show the forth picture on desktop

.brands-carousel > picture:last-child {
  display: none;
}
Enter fullscreen mode Exit fullscreen mode

On mobile we'll make some small adjustments, instead of 20s for 3, well have 27s for 4 images, each image gets 1/4 of the time on stage.

@media screen and (max-width: 766px) {
  .brands-carousel > picture {
    animation: carousel-responsive 27s linear infinite;
  }

  .brands-carousel > picture:nth-child(1) {
    animation-name: first-picture-responsive, carousel-responsive;
    animation-duration: 27s;
    animation-iteration-count: 1, infinite;
    animation-delay: 0s, 27s;
  }

  .brands-carousel > picture:nth-child(2) {
    animation-delay: Calc(27s * .25);
  }
  .brands-carousel > picture:nth-child(3) {
    animation-delay: Calc(27s * .50);
  }
  .brands-carousel > picture:nth-child(4) {
    animation-delay: Calc(27s * .75);
    display: block;
  }
}
Enter fullscreen mode Exit fullscreen mode

Finishing up

If you want to change the time of the full animate loop replace the 20s with your new full time. To change how long a transition is on the screen reduce the 7.5% to a smaller value (and reduce the 40.5% by the same amount). To make any other change (i.e. increasing the length of time the image is static, you'll need to compute that based on 33% of the total time and then recalculate the transition percentage.

Right now the static image is 7.5% to 33% that's 25.5% of 20s (5.1s) on the screen. If you want that to be 6s on the screen without reducing the transition time (7.5% * 20s = 1.5s). Calculate the total new time 6 / .255 = 23.5s for the full animation and then new transition percentages are 1.5s / 23.5s = 6.4% so the new keyframes would be

/* # Updating all the 20s => 23.5s */
@keyframes carousel {
  0% { transform: translateX(100%); }
  6.4%, 33% { transform: translateX(0); }
  39.4%, 100% { transform: translateX(-100%); }
}
Enter fullscreen mode Exit fullscreen mode

And that's it.

Here's a link to the code

Discussion (0)

pic
Editor guide