How to create a carousel with CSS scroll snap

  1. Create A carousel
  2. The interesting parts
    1. How to hide the container’s scrollbar?
    2. How to scroll to a specific element?
    3. How to make a loop carousel?

A carousel is used to crycle through elements:

Step 1. Add HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="parentEl">
<div id="container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item">7</div>
</div>

<div class="controls">
<button class="toggle" data-toggle="prev">Prev</button>
<button class="toggle" data-toggle="next">Next</button>
</div>

<div class="dots" style="text-align: center"></div>

</div>

Step 2. Add CSS:

Style the next and previous buttons, and the dots:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#container {
scroll-snap-type: x mandatory;
display: flex;
overflow-x: scroll;
width: 100%;
}

.item {
border: 1px solid red;
min-width: 33%;
height: auto;
scroll-snap-align: start;
}

#container {
scroll-behavior: smooth;
}

/* Hide the horizontal scroll bar */

/* Hide scrollbar for Chrome, Safari and Opera */
#container::-webkit-scrollbar {
display: none;
}

/* Hide scrollbar for IE, Edge and Firefox */
#container {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}

/** Controls **/
.controls {
padding: 1em;
text-align: center;
}

.controls button {
background: #aaa;
border: 0;
border-radius: 0.25em;
color: #eee;
padding: 0.5em 1em;
}

.controls button:hover,
.controls button:focus {
background: orange;
}

/* The dots/bullets/indicators */
.dot {
cursor: pointer;
height: 13px;
width: 13px;
margin: 0 2px;
background-color: #bbb;
border-radius: 50%;
display: inline-block;
transition: background-color 0.6s ease;
}

.active, .dot:hover {
background-color: #717171;
}

Step 3. Add JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
const parentEl = document.querySelector('.parentEl');
const container = document.querySelector('#container');
const dotsWrapperEl = document.querySelector('.dots');
const items = container.querySelectorAll('.item');
const nextBtn = parentEl.querySelector('.controls [data-toggle="next"]');
const prevBtn = parentEl.querySelector('.controls [data-toggle="prev"]');
let currentIndex = 0;

nextBtn.addEventListener('click', function(e) {
currentIndex = (currentIndex + 1) % items.length;
updateUI();
});

prevBtn.addEventListener('click', function(e) {
currentIndex--;
if (currentIndex < 0) currentIndex = items.length - 1;
updateUI();
});

dotsWrapperEl.addEventListener('click', function(e) {
const target = e.target;
const dotList = dotsWrapperEl.querySelectorAll('.dot');
if (target.classList.contains('dot')) {
const index = getDotIndex(target, dotList);
currentIndex = index;
updateUI();
}
});

function getDotIndex(target, dotList) {
for (let i = 0; i < dotList.length; i++) {
if (dotList[i] === target) return i;
}
return -1;
}

function updateUI() {
dotsWrapperEl.querySelector('.active')?.classList.remove('active');
dotsWrapperEl.querySelectorAll('.dot')[currentIndex].classList.add('active');

const ar = getOrder(currentIndex);
for (let i = 0; i < items.length; i++) {
items[i].style.order = ar[i];
}

items[currentIndex].scrollIntoView({block: "start", inline: "start"});
}

function getOrder(rotateCount = 0) {
const arr = Array.from({length: items.length}, (x, i) => i + 1);
arr.push(arr.shift());
if (rotateCount == 0) return arr;
const ar = arr.splice(items.length - rotateCount, rotateCount);
arr.splice(0, 0, ...ar);
return arr;
}

getOrder();
dotsWrapperEl.innerHTML = '<span class="dot"></span>'.repeat(items.length);

updateUI();

The interesting parts

How to hide the container’s scrollbar?

1
2
3
4
5
6
7
8
9
10
11
12
/* Hide the horizontal scroll bar */

/* Hide scrollbar for Chrome, Safari and Opera */
#container::-webkit-scrollbar {
display: none;
}

/* Hide scrollbar for IE, Edge and Firefox */
#container {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}

How to scroll to a specific element?

There are several ways of scrolling to an element on the web page, we prefer the Element interface’s scrollIntoView() method here.

We are using CSS Scroll Snap rather than other approaches to make slides scrollable, this module does not support loop by default. We could leverage the order CSS property in the flexbox layout to control the orders of slides in the carousel, which is a clever solution.

To determine the order of each item, we use a simple algorithm here:

1
2
3
4
5
6
7
8
function getOrder(rotateCount = 0) {
const arr = Array.from({length: items.length}, (x, i) => i + 1);
arr.push(arr.shift());
if (rotateCount == 0) return arr;
const ar = arr.splice(items.length - rotateCount, rotateCount);
arr.splice(0, 0, ...ar);
return arr;
}

All the slides are given an order CSS property with corresponding value, note the first one has a value of 2 instead of 1 by default. Please see LeetCode 189. Rotate Array