🚧 Please excuse my dust. This site is under construction 🚧

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>&larr;</button>
			<button title="Go to the next slide" data-next>&rarr;</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>