Swipers & Slide Shows with Scroll Snap
An exploration of swipers and slide shows using modern browser features like CSS Scroll Snap
A Basic CSS Only Swiper using CSS Scroll Snap
This is a slide show in the sense that you can swipe but it won’t automatically move. With proper page indicators this is basically the Instagram post swiper.
Syntax Basketball
$42.00
<div class="product">
<div class="demo-swiper">
<div class="demo-swiper-slides" data-demo-swiper>
<figure id="slide1" class="demo-swiper-slide">
<img src="/demo-images/1.png" alt="" />
</figure>
<figure id="slide2" class="demo-swiper-slide">
<img src="/demo-images/2.png" alt="" />
</figure>
<figure id="slide3" class="demo-swiper-slide">
<img src="/demo-images/3.png" alt="" />
</figure>
<figure id="slide4" class="demo-swiper-slide">
<img src="/demo-images/4.png" alt="" />
</figure>
</div>
<div class="demo-swiper-indicators">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</div>
<h4>Syntax Basketball</h4>
<p>$42.00</p>
</div>
<style>
.product {
margin-inline: auto;
--demo-swiper-size: 300px;
width: var(--demo-swiper-size);
}
.demo-swiper {
position: relative;
border-radius: 10px;
overflow: hidden;
}
.demo-swiper-slides {
display: flex;
align-items: center;
gap: 10px;
overflow-x: scroll;
scroll-snap-type: x mandatory;
overscroll-behavior-x: contain;
}
.demo-swiper-slide {
margin: 0;
scroll-snap-align: start;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: var(--demo-swiper-size);
}
.demo-swiper-slide > img {
max-width: 100%;
height: 100%;
object-fit: contain;
}
.demo-swiper-indicators {
position: absolute;
bottom: 10px;
inset-inline: 0;
justify-content: center;
display: flex;
gap: 10px;
span {
block-size: 10px;
inline-size: 10px;
border-radius: 50%;
background-color: #ccc;
}
}
</style>
FYI this basketball is really sick and actually for sale here: Syntax Basketball
What is this missing to become a slide show?
- Working Page indicators
- Forward Prev Buttons
- Automatic
- Events? (only if you need it)
A Swiper With Active Indicators
If we want indicators to show what slide we’re on, buttons and timing, we’ll need to add a bit of JavaScript. Let’s do it
Syntax Basketball
$42.00
<script>
const slider = document.querySelector('#demo-swiper-js-slides');
const indicators = document.querySelectorAll('.demo-swiper-js-indicators span');
slider.addEventListener('scroll', () => {
updateActiveIndicator();
});
function updateActiveIndicator() {
const { scrollLeft, clientWidth } = slider;
const activeIndex = Math.round(scrollLeft / clientWidth);
indicators.forEach((indicator, index) => {
if (index === activeIndex) {
indicator.dataset.active = true;
} else {
indicator.dataset.active = false;
}
});
}
updateActiveIndicator();
</script>
<div class="product">
<div class="demo-slide-show">
<div class="demo-slide-show-slides" id="demo-swiper-js-slides">
<figure id="slide1" class="demo-slide-show-slide">
<img src="/demo-images/1.png" alt="" />
</figure>
<figure id="slide2" class="demo-slide-show-slide">
<img src="/demo-images/2.png" alt="" />
</figure>
<figure id="slide3" class="demo-slide-show-slide">
<img src="/demo-images/3.png" alt="" />
</figure>
<figure id="slide4" class="demo-slide-show-slide">
<img src="/demo-images/4.png" alt="" />
</figure>
</div>
<div class="demo-swiper-js-indicators">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</div>
<h4>Syntax Basketball</h4>
<p>$42.00</p>
</div>
<style>
.product {
--demo-slide-show-size: 300px;
margin-inline: auto;
width: var(--demo-slide-show-size);
}
.demo-slide-show {
position: relative;
border-radius: 10px;
}
.slider__nav {
position: absolute;
display: flex;
justify-content: space-between;
width: 140%;
inset: 0 -20%;
z-index: 0;
button {
border: none;
cursor: pointer;
transition: 0.2s ease opacity;
&:first-child {
opacity: 0;
}
}
}
.demo-slide-show-slides {
z-index: 1;
position: relative;
display: flex;
align-items: center;
overflow-x: scroll;
scroll-snap-type: x mandatory;
overscroll-behavior-x: contain;
border-radius: 10px;
}
.demo-slide-show-slide {
margin: 0;
scroll-snap-align: start;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: var(--demo-slide-show-size);
}
.demo-slide-show-slide > img {
max-width: 100%;
height: 100%;
object-fit: contain;
}
.demo-swiper-js-indicators {
position: absolute;
bottom: 10px;
inset-inline: 0;
justify-content: center;
display: flex;
gap: 10px;
z-index: 2;
span {
block-size: 10px;
inline-size: 10px;
border-radius: 50%;
background-color: #ccc;
opacity: 0.8;
&[data-active='true'] {
background-color: #fff;
scale: 1.3;
opacity: 1;
}
}
}
</style>
<!-- TODO clean up classes and names -->
Let’s Make A Simple Slideshow
Syntax Basketball
$42.00
<script>
const slider = document.querySelector('[data-slider]');
const prevButton = document.querySelector('[data-prev]');
const nextButton = document.querySelector('[data-next]');
const indicators = document.querySelectorAll('.demo-slide-show-indicators span');
prevButton.addEventListener('click', () => slide('prev'));
nextButton.addEventListener('click', () => slide('next'));
slider.addEventListener('scroll', () => {
updateButtonsVisibility();
updateActiveIndicator();
});
function slide(direction) {
const { scrollLeft, clientWidth } = slider;
const left = direction === 'prev' ? scrollLeft - clientWidth : scrollLeft + clientWidth;
slider.scroll({ left, behavior: 'smooth' });
}
function updateButtonsVisibility() {
const { scrollLeft, clientWidth, scrollWidth } = slider;
prevButton.style.opacity = scrollLeft === 0 ? '0' : '1';
prevButton.style.pointerEvents = scrollLeft === 0 ? 'none' : 'auto';
nextButton.style.opacity = scrollLeft + clientWidth >= scrollWidth ? '0' : '1';
nextButton.style.pointerEvents = scrollLeft + clientWidth >= scrollWidth ? 'none' : 'auto';
}
function updateActiveIndicator() {
const { scrollLeft, clientWidth } = slider;
const activeIndex = Math.round(scrollLeft / clientWidth);
indicators.forEach((indicator, index) => {
indicator.style.backgroundColor = index === activeIndex ? '#fff' : '#eee';
indicator.style.scale = index === activeIndex ? '1.3' : '1';
});
}
updateButtonsVisibility();
updateActiveIndicator();
</script>
<div class="product">
<div class="demo-slide-show">
<nav class="slider__nav">
<button title="Previous slide" data-prev>←</button>
<button title="Go to the next slide" data-next>→</button>
</nav>
<div class="demo-slide-show-slides" data-slider>
<figure id="slide1" class="demo-slide-show-slide">
<img src="/demo-images/1.png" alt="" />
</figure>
<figure id="slide2" class="demo-slide-show-slide">
<img src="/demo-images/2.png" alt="" />
</figure>
<figure id="slide3" class="demo-slide-show-slide">
<img src="/demo-images/3.png" alt="" />
</figure>
<figure id="slide4" class="demo-slide-show-slide">
<img src="/demo-images/4.png" alt="" />
</figure>
</div>
<div class="demo-slide-show-indicators">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</div>
<h4>Syntax Basketball</h4>
<p>$42.00</p>
</div>
<style>
.product {
--demo-slide-show-size: 300px;
margin-inline: auto;
width: var(--demo-slide-show-size);
}
.demo-slide-show {
position: relative;
border-radius: 10px;
}
.slider__nav {
position: absolute;
display: flex;
justify-content: space-between;
width: 140%;
inset: 0 -20%;
z-index: 0;
button {
border: none;
cursor: pointer;
transition: 0.2s ease opacity;
&:first-child {
opacity: 0;
}
}
}
.demo-slide-show-slides {
z-index: 1;
position: relative;
display: flex;
align-items: center;
overflow-x: scroll;
scroll-snap-type: x mandatory;
overscroll-behavior-x: contain;
}
.demo-slide-show-slide {
margin: 0;
scroll-snap-align: start;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: var(--demo-slide-show-size);
}
.demo-slide-show-slide > img {
max-width: 100%;
height: 100%;
object-fit: contain;
}
.demo-slide-show-indicators {
position: absolute;
bottom: 10px;
inset-inline: 0;
justify-content: center;
display: flex;
gap: 10px;
z-index: 2;
span {
block-size: 10px;
inline-size: 10px;
border-radius: 50%;
background-color: #ccc;
}
}
</style>