Improvements made:
source: https://alpinejs.dev/component/carousel
This component is subject to a product license.
<!-- Alpine Plugins -->
<script defer src="https://unpkg.com/@alpinejs/[email protected]/dist/cdn.min.js"></script>
<!-- Alpine Core -->
<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
<style>
[x-cloak] {
display: none !important;
}
/* Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
}
</style>
<!--<h1 x-data="{ message: 'I ❤️ Alpine' }" x-text="message"></h1>-->
<!-- Smooth Scroll polyfill, for Safari -->
<script src="https://unpkg.com/[email protected]/dist/smoothscroll.js"></script>
<div class="h-screen flex justify-center items-center">
<div x-data="carousel" @resize.window="skip()" class="flex w-full flex-col">
<div @keydown.right="next" @keydown.left="prev" tabindex="0" role="region" aria-labelledby="carousel-label" class="flex space-x-6">
<h2 id="carousel-label" class="sr-only" hidden>Carousel</h2>
<!-- Prev Button -->
<button @click="prev" class="text-6xl">
<span aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" />
</svg>
</span>
<span class="sr-only">Skip to previous slide page</span>
</button>
<span id="carousel-content-label" class="sr-only" hidden>Carousel</span>
<ul x-ref="slider" tabindex="0" role="listbox" aria-labelledby="carousel-content-label" class="flex w-full snap-x snap-mandatory overflow-x-scroll no-scrollbar">
<li class="w-full sm:w-1/3 flex-shrink-0 snap-start flex flex-col items-center justify-center p-2" role="option">
<img class="mt-2 w-full" src="https://picsum.photos/400/200?random=1" alt="placeholder image">
<button x-intersect:enter="$el.removeAttribute('tabindex')" x-intersect:leave="$el.setAttribute('tabindex', '-1')" class="px-4 py-2 text-sm">Do Something
</button>
</li>
<li class="w-full sm:w-1/3 flex-shrink-0 snap-start flex flex-col items-center justify-center p-2" role="option">
<img class="mt-2 w-full" src="https://picsum.photos/400/200?random=2" alt="placeholder image">
<button x-bind="focusableWhenVisible" class="px-4 py-2 text-sm">Do Something</button>
</li>
<li class="w-full sm:w-1/3 flex-shrink-0 snap-start flex flex-col items-center justify-center p-2" role="option">
<img class="mt-2 w-full" src="https://picsum.photos/400/200?random=3" alt="placeholder image">
<button x-bind="focusableWhenVisible" class="px-4 py-2 text-sm">Do Something</button>
</li>
<li class="w-full sm:w-1/3 flex-shrink-0 snap-start flex flex-col items-center justify-center p-2" role="option">
<img class="mt-2 w-full" src="https://picsum.photos/400/200?random=4" alt="placeholder image">
<button x-bind="focusableWhenVisible" class="px-4 py-2 text-sm">Do Something</button>
</li>
<li class="w-full sm:w-1/3 flex-shrink-0 snap-start flex flex-col items-center justify-center p-2" role="option">
<img class="mt-2 w-full" src="https://picsum.photos/400/200?random=5" alt="placeholder image">
<button x-bind="focusableWhenVisible" class="px-4 py-2 text-sm">Do Something</button>
</li>
<li class="w-full sm:w-1/3 flex-shrink-0 snap-start flex flex-col items-center justify-center p-2" role="option">
<img class="mt-2 w-full" src="https://picsum.photos/400/200?random=6" alt="placeholder image">
<button x-bind="focusableWhenVisible" class="px-4 py-2 text-sm">Do Something</button>
</li>
</ul>
<!-- Next Button -->
<button @click="next">
<span aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
</svg>
</span>
<span class="sr-only">Skip to next slide page</span>
</button>
</div>
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('carousel', () => ({
// function to dynamically set the number of slides based on viewport size, using multiple ternary operators
skip() {
return window.innerWidth < 640 ? 1 // slides to skip on screens < 640px
:
window.innerWidth < 768 ? 2 // slides to skip on screens < 768px
:
3 // default slides to skip (larger screens)
},
next() {
this.to((current, offset) => current + (offset * this.skip()))
},
prev() {
this.to((current, offset) => current - (offset * this.skip()))
},
to(strategy) {
let slider = this.$refs.slider;
let current = slider.scrollLeft;
let offset = slider.firstElementChild.getBoundingClientRect().width;
slider.scrollTo({
left: strategy(current, offset),
behavior: 'smooth'
})
},
focusableWhenVisible: {
'x-intersect:enter'() {
this.$el.removeAttribute('tabindex')
},
'x-intersect:leave'() {
this.$el.setAttribute('tabindex', '-1')
}
}
}))
})
</script>
<!-- Alpine Plugins -->
<script defer src="https://unpkg.com/@alpinejs/[email protected]/dist/cdn.min.js"></script>
<!-- Alpine Core -->
<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
<style>
[x-cloak] {
display: none !important;
}
/* Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
</style>
<!--<h1 x-data="{ message: 'I ❤️ Alpine' }" x-text="message"></h1>-->
<!-- Smooth Scroll polyfill, for Safari -->
<script src="https://unpkg.com/[email protected]/dist/smoothscroll.js"></script>
<div class="h-screen flex justify-center items-center">
<div x-data="carousel"
@resize.window="skip()"
class="flex w-full flex-col"
>
<div @keydown.right="next"
@keydown.left="prev"
tabindex="0"
role="region"
aria-labelledby="carousel-label"
class="flex space-x-6"
>
<h2 id="carousel-label" class="sr-only" hidden>Carousel</h2>
<!-- Prev Button -->
<button @click="prev" class="text-6xl">
<span aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-gray-600" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round"
d="M15 19l-7-7 7-7"/></svg>
</span>
<span class="sr-only">Skip to previous slide page</span>
</button>
<span id="carousel-content-label" class="sr-only" hidden>Carousel</span>
<ul
x-ref="slider"
tabindex="0"
role="listbox"
aria-labelledby="carousel-content-label"
class="flex w-full snap-x snap-mandatory overflow-x-scroll no-scrollbar"
>
<li class="w-full sm:w-1/3 flex-shrink-0 snap-start flex flex-col items-center justify-center p-2"
role="option">
<img class="mt-2 w-full" src="https://picsum.photos/400/200?random=1" alt="placeholder image">
<button x-intersect:enter="$el.removeAttribute('tabindex')"
x-intersect:leave="$el.setAttribute('tabindex', '-1')"
class="px-4 py-2 text-sm">Do Something
</button>
</li>
<li class="w-full sm:w-1/3 flex-shrink-0 snap-start flex flex-col items-center justify-center p-2"
role="option">
<img class="mt-2 w-full" src="https://picsum.photos/400/200?random=2" alt="placeholder image">
<button x-bind="focusableWhenVisible" class="px-4 py-2 text-sm">Do Something</button>
</li>
<li class="w-full sm:w-1/3 flex-shrink-0 snap-start flex flex-col items-center justify-center p-2"
role="option">
<img class="mt-2 w-full" src="https://picsum.photos/400/200?random=3" alt="placeholder image">
<button x-bind="focusableWhenVisible" class="px-4 py-2 text-sm">Do Something</button>
</li>
<li class="w-full sm:w-1/3 flex-shrink-0 snap-start flex flex-col items-center justify-center p-2"
role="option">
<img class="mt-2 w-full" src="https://picsum.photos/400/200?random=4" alt="placeholder image">
<button x-bind="focusableWhenVisible" class="px-4 py-2 text-sm">Do Something</button>
</li>
<li class="w-full sm:w-1/3 flex-shrink-0 snap-start flex flex-col items-center justify-center p-2"
role="option">
<img class="mt-2 w-full" src="https://picsum.photos/400/200?random=5" alt="placeholder image">
<button x-bind="focusableWhenVisible" class="px-4 py-2 text-sm">Do Something</button>
</li>
<li class="w-full sm:w-1/3 flex-shrink-0 snap-start flex flex-col items-center justify-center p-2"
role="option">
<img class="mt-2 w-full" src="https://picsum.photos/400/200?random=6" alt="placeholder image">
<button x-bind="focusableWhenVisible" class="px-4 py-2 text-sm">Do Something</button>
</li>
</ul>
<!-- Next Button -->
<button @click="next">
<span aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-gray-600" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="3"><path stroke-linecap="round" stroke-linejoin="round"
d="M9 5l7 7-7 7"/></svg>
</span>
<span class="sr-only">Skip to next slide page</span>
</button>
</div>
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('carousel', () => ({
// function to dynamically set the number of slides based on viewport size, using multiple ternary operators
skip() {
return window.innerWidth < 640 ? 1 // slides to skip on screens < 640px
: window.innerWidth < 768 ? 2 // slides to skip on screens < 768px
: 3 // default slides to skip (larger screens)
},
next() {
this.to((current, offset) => current + (offset * this.skip()))
},
prev() {
this.to((current, offset) => current - (offset * this.skip()))
},
to(strategy) {
let slider = this.$refs.slider;
let current = slider.scrollLeft;
let offset = slider.firstElementChild.getBoundingClientRect().width;
slider.scrollTo({left: strategy(current, offset), behavior: 'smooth'})
},
focusableWhenVisible: {
'x-intersect:enter'() {
this.$el.removeAttribute('tabindex')
},
'x-intersect:leave'() {
this.$el.setAttribute('tabindex', '-1')
}
}
}))
})
</script>
/* No context defined. */