Have you ever built an advanced slider for a project? If so, you might have taken advantage of any one of a number of JavaScript carousels. In the past, I’ve covered two of them: slick and Owl. Today, I’ll introduce another well-known one: Swiper.
Our Slider Project
Check what we’ll be building:
Be sure to open the demo on a large screen and resize your window to see how the page layout changes.
This tutorial has been updated with another demo that reveals a custom cursor on sliders’ hover—check out the demo below and the added extra section! Be sure to view the demo from a device with hover capability.
1. Identify the Layout
To get started, let’s first identify the project scope.
Today’s demo is a web page dedicated to Tanzania, a country with immense beauty. To set up the page, we’ll grab some content from Wikipedia and images from Unsplash.
Let’s determine how the page layout will appear on various screens.
The Mobile Layout
On small screens (<768px), its appearance will be like this:
Notice that each slider shows the first slide and half of the second one.
The Tablet Layout
Next, on medium screens (≥768px), its appearance will change as follows:
Notice that the first slider shows the first two slides and half of the third one, while the second slider still shows two slides.
The Desktop Layout
Finally, on large screens (≥1200px), it will have this appearance:
Again, consider that the first slider shows the first three slides and half of the fourth one, while the second slider shows the first two slides and half of the third one.
Also, pay attention to another thing: the right side of the first slider is aligned to the container width that, as we’ll see, will be 1100px. In the same way, the left slide of the second slider is aligned to the container width.
The red area indicates the empty space that will be increased as the viewport becomes bigger and bigger.
2. Define the HTML Markup
The markup for our page will consist of four sections:
- The first and third sections will contain info about Tanzania taken from Wikipedia.
- The second and fourth sections will include two equal carousels that will display Tanzania through Unsplash photos. These sections will only have a different class that will determine their layout. That said, the second section will have the
section-with-right-offset
class, while the fourth section thesection-with-left-offset
one.
Here’s the page structure:
1 |
<section class="mt-5"> |
2 |
<div class="container">...</div> |
3 |
</section>
|
4 |
|
5 |
<section class="section-with-carousel section-with-right-offset position-relative mt-5"> |
6 |
<div class="container"> |
7 |
<h2 class="mb-3">...</h2> |
8 |
</div>
|
9 |
<div class="carousel-wrapper"> |
10 |
<div class="swiper"> |
11 |
<div class="swiper-wrapper"> |
12 |
<div class="swiper-slide"> |
13 |
<figure>
|
14 |
<img width="640" height="480" src="tanzania1.jpg" alt=""> |
15 |
<figcaption>
|
16 |
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20"> |
17 |
<path d="M12 13.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z"></path> |
18 |
<path fill-rule="evenodd" d="M19.071 3.429C15.166-.476 8.834-.476 4.93 3.429c-3.905 3.905-3.905 10.237 0 14.142l.028.028 5.375 5.375a2.359 2.359 0 003.336 0l5.403-5.403c3.905-3.905 3.905-10.237 0-14.142zM5.99 4.489A8.5 8.5 0 0118.01 16.51l-5.403 5.404a.859.859 0 01-1.214 0l-5.378-5.378-.002-.002-.023-.024a8.5 8.5 0 010-12.02z"></path> |
19 |
</svg>
|
20 |
Nungwi, Zanzibar, Tanzania |
21 |
</figcaption>
|
22 |
</figure>
|
23 |
</div>
|
24 |
<!-- more slides here -->
|
25 |
</div>
|
26 |
</div>
|
27 |
</div>
|
28 |
<div class="carousel-controls"> |
29 |
<button class="carousel-control carousel-control-left" type="button"> |
30 |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="40" height="40"> |
31 |
<path fill-rule="evenodd" d="M10.78 19.03a.75.75 0 01-1.06 0l-6.25-6.25a.75.75 0 010-1.06l6.25-6.25a.75.75 0 111.06 1.06L5.81 11.5h14.44a.75.75 0 010 1.5H5.81l4.97 4.97a.75.75 0 010 1.06z"></path> |
32 |
</svg>
|
33 |
</button>
|
34 |
<button class="carousel-control carousel-control-right" type="button"> |
35 |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="40" height="40"> |
36 |
<path fill-rule="evenodd" d="M13.22 19.03a.75.75 0 001.06 0l6.25-6.25a.75.75 0 000-1.06l-6.25-6.25a.75.75 0 10-1.06 1.06l4.97 4.97H3.75a.75.75 0 000 1.5h14.44l-4.97 4.97a.75.75 0 000 1.06z"></path> |
37 |
</svg>
|
38 |
</button>
|
39 |
</div>
|
40 |
<div class="swiper-pagination"></div> |
41 |
</section>
|
42 |
|
43 |
<section class="mt-5"> |
44 |
<div class="container">...</div> |
45 |
</section>
|
46 |
|
47 |
<section class="section-with-carousel section-with-left-offset position-relative mt-5"> |
48 |
<!-- content same as second section -->
|
49 |
</section>
|
3. Specify the Main Styles
Let’s now concentrate on the most important styles of our page.
Here are the remarkable things:
- The container will have a maximum width of 1100px.
- The initial dimensions of the Unsplash images won’t be equal. With that in mind, the carousel images will have a fixed height that will vary across the screen sizes. Here we’ll use the
object-fit: cover
property value for fitting the images within the container. Alternatively, we could have added the images as background ones. - By default, all image captions will be hidden apart from the caption of the active slide. As the active slide changes, the associated caption will appear with small slide animation.
Here are the associated styles:
1 |
.container { |
2 |
max-width: 1100px; |
3 |
}
|
4 |
|
5 |
.section-with-carousel .swiper-slide figure { |
6 |
position: relative; |
7 |
overflow: hidden; |
8 |
}
|
9 |
|
10 |
.section-with-carousel .swiper-slide img { |
11 |
width: 100%; |
12 |
height: 320px; |
13 |
object-fit: cover; |
14 |
}
|
15 |
|
16 |
.section-with-carousel .swiper-slide figcaption { |
17 |
position: absolute; |
18 |
bottom: 0; |
19 |
left: 0; |
20 |
right: 0; |
21 |
transform: translateY(20%); |
22 |
display: flex; |
23 |
align-items: baseline; |
24 |
justify-content: center; |
25 |
padding: 20px; |
26 |
text-align: center; |
27 |
opacity: 0; |
28 |
visibility: hidden; |
29 |
color: white; |
30 |
background: rgba(0, 0, 0, 0.5); |
31 |
transition: all 0.4s; |
32 |
}
|
33 |
|
34 |
.section-with-carousel .swiper-slide figcaption svg { |
35 |
flex-shrink: 0; |
36 |
fill: white; |
37 |
margin-right: 10px; |
38 |
}
|
39 |
|
40 |
.section-with-carousel .swiper-slide-active figcaption { |
41 |
opacity: 1; |
42 |
visibility: visible; |
43 |
transform: none; |
44 |
}
|
45 |
|
46 |
.section-with-carousel .carousel-controls { |
47 |
position: absolute; |
48 |
top: 50%; |
49 |
left: 0; |
50 |
right: 0; |
51 |
transform: translateY(-50%); |
52 |
display: flex; |
53 |
justify-content: space-between; |
54 |
padding: 0 12px; |
55 |
z-index: 1; |
56 |
}
|
57 |
|
58 |
.section-with-carousel .carousel-controls .carousel-control { |
59 |
opacity: 0.25; |
60 |
transition: opacity 0.3s; |
61 |
}
|
62 |
|
63 |
.section-with-carousel .carousel-controls .carousel-control:hover { |
64 |
opacity: 1; |
65 |
}
|
66 |
|
67 |
@media (min-width: 768px) { |
68 |
.section-with-carousel .swiper-slide img { |
69 |
height: 370px; |
70 |
}
|
71 |
}
|
72 |
|
73 |
@media (min-width: 1200px) { |
74 |
.section-with-carousel .swiper-slide img { |
75 |
height: 420px; |
76 |
}
|
77 |
|
78 |
.section-with-carousel .carousel-controls { |
79 |
padding: 0 50px; |
80 |
}
|
81 |
}
|
Customize Dot Navigation
The initial appearance of the dot navigation is this one:
Why not make it more informative and attractive? To achieve this, we’ll add some styles and update its default markup during the plugin initialization.
Here’s the updated navigation:
The required styles:
1 |
.section-with-carousel .swiper-pagination-bullets { |
2 |
position: static; |
3 |
display: flex; |
4 |
justify-content: center; |
5 |
margin-top: 10px; |
6 |
}
|
7 |
|
8 |
.section-with-carousel .swiper-pagination-bullets .swiper-pagination-bullet { |
9 |
display: flex; |
10 |
flex-direction: column; |
11 |
align-items: center; |
12 |
justify-content: center; |
13 |
width: auto; |
14 |
height: auto; |
15 |
background: transparent; |
16 |
opacity: 0.5; |
17 |
margin: 0 8px; |
18 |
border-radius: 0; |
19 |
transition: opacity 0.3s; |
20 |
}
|
21 |
|
22 |
.section-with-carousel
|
23 |
.swiper-pagination-bullets
|
24 |
.swiper-pagination-bullet
|
25 |
.line { |
26 |
width: 3px; |
27 |
height: 3px; |
28 |
background: black; |
29 |
transition: transform 0.3s; |
30 |
}
|
31 |
|
32 |
.section-with-carousel
|
33 |
.swiper-pagination-bullets
|
34 |
.swiper-pagination-bullet
|
35 |
.number { |
36 |
opacity: 0; |
37 |
transform: translateY(-7px); |
38 |
transition: all 0.3s; |
39 |
}
|
40 |
|
41 |
.section-with-carousel
|
42 |
.swiper-pagination-bullets
|
43 |
.swiper-pagination-bullet.swiper-pagination-bullet-active { |
44 |
opacity: 1; |
45 |
}
|
46 |
|
47 |
.section-with-carousel
|
48 |
.swiper-pagination-bullets
|
49 |
.swiper-pagination-bullet.swiper-pagination-bullet-active
|
50 |
.line { |
51 |
transform: scaleX(8); |
52 |
}
|
53 |
|
54 |
.section-with-carousel
|
55 |
.swiper-pagination-bullets
|
56 |
.swiper-pagination-bullet.swiper-pagination-bullet-active
|
57 |
.number { |
58 |
opacity: 1; |
59 |
transform: none; |
60 |
}
|
Be sure to click on a dot to see the little slide animation that happens.
4. Add the JavaScript
At this point, we’re ready to turn our attention to JavaScript.
Offset Carousel Sections
As you can see from the previous visualizations, the carousel sections will be full-screen on screens up to 1199px wide. On larger screens, their left or right side will stop being full-screen and be aligned to the container width. As discussed earlier, this behavior will be determined by the section-with-left-offset
and section-with-right-offset
classes. This way, we’ll be able to generate unique layouts that won’t be limited to sections that follow a grid system.
Here’s the JavaScript code that implements this functionality:
1 |
createOffsets(); |
2 |
window.addEventListener("resize", createOffsets); |
3 |
|
4 |
function createOffsets() { |
5 |
const sectionWithLeftOffset = document.querySelector( |
6 |
".section-with-left-offset" |
7 |
);
|
8 |
const sectionWithLeftOffsetCarouselWrapper = sectionWithLeftOffset.querySelector( |
9 |
".carousel-wrapper" |
10 |
);
|
11 |
const sectionWithRightOffset = document.querySelector( |
12 |
".section-with-right-offset" |
13 |
);
|
14 |
const sectionWithRightOffsetCarouselWrapper = sectionWithRightOffset.querySelector( |
15 |
".carousel-wrapper" |
16 |
);
|
17 |
const offset = (window.innerWidth - 1100) / 2; |
18 |
const mqLarge = window.matchMedia("(min-width: 1200px)"); |
19 |
|
20 |
if (sectionWithLeftOffset && mqLarge.matches) { |
21 |
sectionWithLeftOffsetCarouselWrapper.style.marginLeft = offset + "px"; |
22 |
} else { |
23 |
sectionWithLeftOffsetCarouselWrapper.style.marginLeft = 0; |
24 |
}
|
25 |
|
26 |
if (sectionWithRightOffset && mqLarge.matches) { |
27 |
sectionWithRightOffsetCarouselWrapper.style.marginRight = offset + "px"; |
28 |
} else { |
29 |
sectionWithRightOffsetCarouselWrapper.style.marginRight = 0; |
30 |
}
|
31 |
}
|
Initialize Swiper
This is the last required step to initialize Swiper. Here, we pass as part of the configuration object all our customizations. Look at the relevant code snippet below:
1 |
const sectionsWithCarousel = document.querySelectorAll( |
2 |
".section-with-carousel" |
3 |
);
|
4 |
|
5 |
for (const section of sectionsWithCarousel) { |
6 |
let slidesPerView = [1.5, 2.5, 3.5]; |
7 |
if (section.classList.contains("section-with-left-offset")) { |
8 |
slidesPerView = [1.5, 1.5, 2.5]; |
9 |
}
|
10 |
const swiper = section.querySelector(".swiper"); |
11 |
new Swiper(swiper, { |
12 |
slidesPerView: slidesPerView[0], |
13 |
spaceBetween: 15, |
14 |
loop: true, |
15 |
lazyLoading: true, |
16 |
keyboard: { |
17 |
enabled: true |
18 |
},
|
19 |
navigation: { |
20 |
prevEl: section.querySelector(".carousel-control-left"), |
21 |
nextEl: section.querySelector(".carousel-control-right") |
22 |
},
|
23 |
pagination: { |
24 |
el: section.querySelector(".swiper-pagination"), |
25 |
clickable: true, |
26 |
renderBullet: function (index, className) { |
27 |
return `<div class=${className}> |
28 |
<span class="number">${index + 1}</span> |
29 |
<span class="line"></span>
|
30 |
</div>`; |
31 |
}
|
32 |
},
|
33 |
breakpoints: { |
34 |
768: { |
35 |
slidesPerView: slidesPerView[1] |
36 |
},
|
37 |
1200: { |
38 |
slidesPerView: slidesPerView[2] |
39 |
}
|
40 |
}
|
41 |
});
|
42 |
}
|
As you can see, we pass in an array the number of slides that should appear depending on the viewport width. By using, not only integers but also decimals, we’ll be able to show just a portion of a slide.
Even though it isn’t necessary, the breakpoint values that determine when the number of visible slides changes will match Bootstrap’s breakpoints. Anyhow, be sure to read the API documentation to get a better understanding of what all these configuration parameters do.
EXTRA: Custom Mouse Cursor
Let’s now adjust our demo to showcase a custom cursor each time we hover over a slider.
In actual fact, this isn’t the first time we discuss custom cursors effects. Both I and Jemima have written tutorials about it in the past.
Concerning the current development, let’s highlight the key aspects:
- We’ll use the generic
pointermove
event to detect the hovered element. Note that in this case, themousemove
event won’t work as expected. This has only to do with how Swiper.js works. A workaround as per this Stack Overflow thread is to keep themousemove
event and passtouchStartPreventDefault: false
to the slider config object. However, this solution breaks the swipe functionality on Firefox. Of course, there might be other solutions. Consider the problematic use case in the following demo.
Pointer events are DOM events that detect interaction from a pointing input device such as a mouse, pen/stylus, or touchscreen.
-
We’ll show the cursor only if the hovered element is a descendant of the
.carousel-wrapper
one and the device from where we view the demo allows us to hover over it. To fulfil the last requirement, we’ll use the hover CSS4 media feature which has solid support. That said, as a generic rule, we expect desktop computers and laptops to show the cursor while smartphones don’t. Of course, here things can change under certain conditions like in case we use a stylus/pen on our phone. But let’s keep things simple and don’t add more strict rules to match all possible scenarios.
- By default, the cursor will be a fixed position element. We’ll use the
cursor-x
andcursor-y
CSS variables to update itstop
andleft
values based on the actual position of the mouse pointer. - We’ll override the styles of the
.carousel-controls
element and create additional ones for the child arrows. As you can see from the visualization above, only the left arrow sits within the boundaries of the hovered area. Each time a user hovers over it, the cursor will disappear.
With all the above in mind, let’s examine the required additions in our initial demo:
1. HTML
First, we’ll add the cursor markup:
1 |
<div class="cursor"> |
2 |
<img width="40" height="42" src="cursor-swipe.svg" alt="swipe indicator"> |
3 |
</div>
|
2. CSS
Then, we’ll continue with the styles limiting them only to devices with hover support:
1 |
@media (hover: hover) { |
2 |
.section-with-carousel .carousel-wrapper { |
3 |
cursor: none; |
4 |
}
|
5 |
|
6 |
.section-with-carousel .carousel-controls .carousel-control { |
7 |
position: absolute; |
8 |
top: 50%; |
9 |
transform: translateY(-50%); |
10 |
z-index: 1; |
11 |
}
|
12 |
|
13 |
.section-with-carousel .carousel-controls .carousel-control-left { |
14 |
left: 50px; |
15 |
}
|
16 |
|
17 |
.section-with-carousel .carousel-controls .carousel-control-right { |
18 |
right: 50px; |
19 |
}
|
20 |
|
21 |
.cursor { |
22 |
position: fixed; |
23 |
left: var(--cursor-x); |
24 |
top: var(--cursor-y); |
25 |
width: 120px; |
26 |
height: 120px; |
27 |
display: flex; |
28 |
align-items: center; |
29 |
justify-content: center; |
30 |
color: white; |
31 |
background: black; |
32 |
border-radius: 50%; |
33 |
transform: translate(-50%, -50%) scale(0); |
34 |
z-index: 1; |
35 |
pointer-events: none; |
36 |
opacity: 0; |
37 |
transition: opacity 0.2s, transform 0.2s; |
38 |
}
|
39 |
|
40 |
body.show-custom-cursor .cursor { |
41 |
opacity: 1; |
42 |
transform: translate(-50%, -50%) scale(1); |
43 |
}
|
44 |
}
|
3. JS
We’ll finish with the required JavaScript code:
1 |
const cursor = document.querySelector(".cursor"); |
2 |
const body = document.body; |
3 |
const toggleClass = "show-custom-cursor"; |
4 |
|
5 |
function pointermoveHandler(e) { |
6 |
const target = e.target; |
7 |
if ( |
8 |
e.target.closest(".carousel-wrapper") && |
9 |
window.matchMedia("(hover: hover)").matches |
10 |
) { |
11 |
body.classList.add(toggleClass); |
12 |
cursor.style.setProperty("--cursor-x", `${e.clientX}px`); |
13 |
cursor.style.setProperty("--cursor-y", `${e.clientY}px`); |
14 |
} else { |
15 |
body.classList.remove(toggleClass); |
16 |
}
|
17 |
}
|
18 |
document.addEventListener("pointermove", pointermoveHandler); |
For better understanding, the original demo’s CSS and JS stayed untouched, and all the new code has been added at the bottom with proper comments.
Conclusion
And we’re done, folks! In this tutorial, we created an asymmetric page layout with just a few lines of JavaScript code and the power of Swiper.js.
We only covered just the very basics of this plugin. There are so many more advanced sliders that you can build with minimal effort. Just get some inspiration from a source like Dribbble and practice yourselves! This is the best way to learn!
Let’s look again at what we built:
As a bonus, we covered how to impress our visitors by adding an animated mouse cursor effect to the sliders. To make it a bit more accurate and limit the functionality only to desktop devices, we took advantage of a CSS4 media query. Of course, nothing stops you from incorporating this effect in your own sliders or pages.
As always, thanks a lot for reading!