Created
May 6, 2021 22:12
-
-
Save DavidColby/3d31c47fc0345a49f50774540d2231dc to your computer and use it in GitHub Desktop.
Implementation of a horizontal slider component with Stimulus and Tailwind CSS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
This code is meant to accompany the guide originally published at https://colby.so/posts/building-a-horizontal-slider-with-stimulus-and-tailwind | |
It intentionally pulls in Tailwind CSS and Stimulus without a build system to simplify the guide. You shouldn't do this with a real application, | |
you should use a build system like webpack! | |
--> | |
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Horizontal slider with Stimulus and Tailwind</title> | |
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet"> | |
<script src="https://unpkg.com/stimulus/dist/stimulus.umd.js"></script> | |
<script> | |
(() => { | |
const application = Stimulus.Application.start() | |
application.register("slider", class extends Stimulus.Controller { | |
static get targets() { | |
return [ "scrollContainer", "image", "indicator" ] | |
} | |
initialize() { | |
this.observer = new IntersectionObserver(this.onIntersectionObserved.bind(this), { | |
root: this.scrollContainerTarget, | |
threshold: 0.5 | |
}) | |
this.imageTargets.forEach(image => { | |
this.observer.observe(image) | |
}) | |
} | |
onIntersectionObserved(entries) { | |
entries.forEach(entry => { | |
if (entry.intersectionRatio > 0.5) { | |
const intersectingIndex = this.imageTargets.indexOf(entry.target) | |
this.indicatorTargets[intersectingIndex].classList.add("bg-blue-900") | |
} | |
else { | |
const intersectingIndex = this.imageTargets.indexOf(entry.target) | |
this.indicatorTargets[intersectingIndex].classList.remove("bg-blue-900") | |
} | |
}) | |
} | |
scrollTo() { | |
const imageId = event.target.dataset.imageId | |
const imageElement = document.getElementById(imageId) | |
const imageCoordinates = imageElement.getBoundingClientRect() | |
this.scrollContainerTarget.scrollTo({ left: (this.scrollContainerTarget.scrollLeft + imageCoordinates.left), top: false, behavior: "smooth" }) | |
} | |
}) | |
})() | |
</script> | |
<style type="text/css"> | |
.gallery-item { | |
scroll-snap-align: start; | |
} | |
.gallery { | |
-webkit-overflow-scrolling: touch; | |
scroll-snap-type: x mandatory; | |
} | |
</style> | |
</head> | |
<body> | |
<main> | |
<div class="flex flex-col my-24" data-controller="slider"> | |
<h1 class="text-3xl text-gray-900 text-center mb-4">Our slider's title</h1> | |
<div class="flex overflow-x-scroll hide-scroll-bar overscroll-x-contain gallery" data-slider-target="scrollContainer"> | |
<div class="w-96 h-64 px-4 flex-shrink-0 gallery-item" data-slider-target="image" id="1"> | |
<img src="https://images.unsplash.com/photo-1542291026-7eec264c27ff?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8c2hvZXN8ZW58MHx8MHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=900&q=60" /> | |
</div> | |
<div class="w-96 h-64 px-4 flex-shrink-0 gallery-item" data-slider-target="image" id="2"> | |
<img src="https://images.unsplash.com/photo-1542291026-7eec264c27ff?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8c2hvZXN8ZW58MHx8MHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=900&q=60" /> | |
</div> | |
<div class="w-96 h-64 px-4 flex-shrink-0 gallery-item" data-slider-target="image" id="3"> | |
<img src="https://images.unsplash.com/photo-1542291026-7eec264c27ff?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8c2hvZXN8ZW58MHx8MHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=900&q=60" /> | |
</div> | |
<div class="w-96 h-64 px-4 flex-shrink-0 gallery-item" data-slider-target="image" id="4"> | |
<img src="https://images.unsplash.com/photo-1542291026-7eec264c27ff?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8c2hvZXN8ZW58MHx8MHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=900&q=60" /> | |
</div> | |
<div class="w-96 h-64 px-4 flex-shrink-0 gallery-item" data-slider-target="image" id="5"> | |
<img src="https://images.unsplash.com/photo-1542291026-7eec264c27ff?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8c2hvZXN8ZW58MHx8MHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=900&q=60" /> | |
</div> | |
<div class="w-96 h-64 px-4 flex-shrink-0 gallery-item" data-slider-target="image" id="6"> | |
<img src="https://images.unsplash.com/photo-1542291026-7eec264c27ff?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8c2hvZXN8ZW58MHx8MHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=900&q=60" /> | |
</div> | |
</div> | |
<div class="flex mx-auto my-8"> | |
<ul class="flex justify-center"> | |
<!-- Note that we have one <li> for each image in our gallery --> | |
<li class="h-6 w-6 rounded-full mx-2 cursor-pointer bg-gray-500" data-slider-target="indicator" data-image-id="1" data-action="click->slider#scrollTo"></li> | |
<li class="h-6 w-6 rounded-full mx-2 cursor-pointer bg-gray-500" data-slider-target="indicator" data-image-id="2" data-action="click->slider#scrollTo"></li> | |
<li class="h-6 w-6 rounded-full mx-2 cursor-pointer bg-gray-500" data-slider-target="indicator" data-image-id="3" data-action="click->slider#scrollTo"></li> | |
<li class="h-6 w-6 rounded-full mx-2 cursor-pointer bg-gray-500" data-slider-target="indicator" data-image-id="4" data-action="click->slider#scrollTo"></li> | |
<li class="h-6 w-6 rounded-full mx-2 cursor-pointer bg-gray-500" data-slider-target="indicator" data-image-id="5" data-action="click->slider#scrollTo"></li> | |
<li class="h-6 w-6 rounded-full mx-2 cursor-pointer bg-gray-500" data-slider-target="indicator" data-image-id="6" data-action="click->slider#scrollTo"></li> | |
</ul> | |
</div> | |
</div> | |
</main> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment