
Your latest project needs a client logo carousel. One in two sites requires some kind of logo carousel, either client logos, or provider logos.
In order to keep Google’s CLS metric in check and also load the carousel instantly, here’s the fastest solution. This logo carousel works with JPEG, PNG, SVG, WebP and GIF images. It also works with text, or other HTML elements, provided you set a fixed slide width.
There are 2 tricks you need to remember:
- Have 2 identical sets of logos.
- Learn the formula.
Let’s break it down.
The HTML structure is simple. We have the main slider
element, with a track inside and a set of images:
<div class="slider">
<div class="slide-track-1">
<div class="slide">
<img src="/path/to/image1.png" alt="">
</div>
<div class="slide">
<img src="/path/to/image2.png" alt="">
</div>
</div>
</div>
Add as many images as you want, then add them again, like below:
<div class="slider">
<div class="slide-track-1">
<div class="slide">
<img src="/path/to/image1.png" alt="">
</div>
<div class="slide">
<img src="/path/to/image2.png" alt="">
</div>
<div class="slide">
<img src="/path/to/image1.png" alt="">
</div>
<div class="slide">
<img src="/path/to/image2.png" alt="">
</div>
</div>
</div>
The rest is CSS, which again, is very, very simple. We’ll also compress it, just to prove a point :)
.slider {
background: #efeef2;
height: 75pt;
margin: 0 auto;
overflow: hidden;
position: relative;
cursor: default;
user-select: none;
touch-action: none;
}
.slider:before {
left: 0;
top: 0;
}
.slider:after,
.slider:before {
background: linear-gradient(
to right,
#efeef2 0%,
hsla(0, 0%, 100%, 0) 100%
);
content: "";
height: 75pt;
position: absolute;
width: 200px;
z-index: 2;
}
.slider:after {
right: 0;
top: 0;
transform: rotateZ(180deg);
}
.slider:after,
.slider:before {
background: linear-gradient(
to right,
#efeef2 0%,
hsla(0, 0%, 100%, 0) 100%
);
content: "";
height: 75pt;
position: absolute;
width: 200px;
z-index: 2;
}
.slider .slide-track-1 {
animation: scroll-l 30s linear infinite;
display: flex;
width: 3500px;
}
.slider .slide-track-2 {
animation: scroll-r 30s linear infinite;
display: flex;
width: 3500px;
}
.slider .slide {
height: 75pt;
width: 250px;
display: flex;
align-items: center;
text-align: center;
}
.slider .slide img {
width: 180px;
padding: 1pc;
vertical-align: middle;
margin: 0 auto;
display: inline-block;
max-width: 100%;
height: auto;
}
@keyframes scroll-l {
0% {
transform: translateX(0);
}
100% {
transform: translateX(calc(-250px * 7));
}
}
@keyframes scroll-r {
100% {
transform: translateX(0);
}
0% {
transform: translateX(calc(-250px * 7));
}
}
Minified and Gzipped is 454 bytes.
.slider{background:#efeef2;height:75pt;margin:0 auto;overflow:hidden;position:relative;cursor:default;user-select:none;touch-action:none}.slider:before{left:0;top:0}.slider:after{right:0;top:0;transform:rotateZ(180deg)}.slider:after,.slider:before{background:linear-gradient(to right,#efeef2 0,hsla(0,0%,100%,0) 100%);content:"";height:75pt;position:absolute;width:200px;z-index:2}.slider .slide-track-1{animation:scroll-l 30s linear infinite;display:flex;width:3500px}.slider .slide-track-2{animation:scroll-r 30s linear infinite;display:flex;width:3500px}.slider .slide{height:75pt;width:250px;display:flex;align-items:center;text-align:center}.slider .slide img{width:180px;padding:1pc;vertical-align:middle;margin:0 auto;display:inline-block;max-width:100%;height:auto}@keyframes scroll-l{0%{transform:translateX(0)}100%{transform:translateX(calc(-250px * 7))}}@keyframes scroll-r{100%{transform:translateX(0)}0%{transform:translateX(calc(-250px * 7))}}
In our example above, note the slide width – 250px – and the total slider track width – 3500px. Our example has 7 images. That is:
7 × 250 = 1750
We also have 2 sets of images, so:
1750 × 2 = 3500
That’s it! Assuming you’d have 10 logos and keeping the 250px width, the total track width would be 5000px:
(10 × 250) × 2 = 5000
Next, let’s make Google happy by lazy loading the images. In most cases, logo carousels are below the fold, so the images don’t need to be loaded. Take a note of your logos’ width and height (they should all be the same) and add these parameters to each image element:
width="300" height="240" alt="Logo description" loading="lazy"
The resulting slide should look like below:
...
<div class="slide">
<img src="/path/to/1.png" width="300" height="240" alt="Logo description" loading="lazy">
</div>
...
See some examples below:

Technical SEO specialist, JavaScript developer and senior full-stack developer. Owner of getButterfly.com.
If you like this article, go ahead and follow me on Twitter or buy me a coffee to support my work!
Very well explained, saved lot of time and code required to implement carousel plugins.
Thank you Ciprian Popescu!
This is exactly what I am looking for…
Just Update the HTML of “slider-track-2” div is missed out, I think so 👍
You are welcome!
I left out the
slider-track-2
element on purpose, as it’s the same structure. It only has a different animation applied to it.Any way to keep it flowing endlessly? Currently it resets when it gets to ‘the end of the line’?
It is actually infinite. Do you have a demo that resets?
The demo on this site resets, at least for me.
I have recorded an animated GIF to show you it’s working here (note: it’s ~60MB):
https://getbutterfly.com/wp-content/uploads/2023/04/a.gif
Mine seems to reset too for some reason! I’ve converted it into css.modules and still getting the same thing.
Try to put it on Codepen, maybe you have something in your code interfering with the animation.