JSFiddle - React, Tailwind, and code Playground
by Alexander Maslakov
HTML
<div class="carousel" x-data="carousel">
<div class="carousel-items" x-ref="items">
<div class="carousel-item">1</div>
<div class="carousel-item">2</div>
<div class="carousel-item">3</div>
</div>
<button
type="button"
class="carousel-button carousel-button-start"
x-bind:disabled="scrolledStart"
x-on:click="prev"
>
←
</button>
<button
type="button"
class="carousel-button carousel-button-end"
x-bind:disabled="scrolledEnd"
x-on:click="next"
>
→
</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
CSS
.carousel {
position: relative;
}
.carousel-items {
display: flex;
width: 100%;
flex-direction: row;
gap: .75rem;
overflow-y: hidden;
overflow-x: scroll;
scrollbar-width: none;
scroll-snap-type: x mandatory;
}
.carousel-item {
display: flex;
aspect-ratio: 16 / 9;
min-width: 100%;
align-items: center;
justify-content: center;
border-radius: .5rem;
background-color: silver;
scroll-snap-align: start;
}
.carousel-button {
position: absolute;
top: 0;
bottom: 0;
margin-top: auto;
margin-bottom: auto;
height: 2rem;
width: 2rem;
border-radius: 9999px;
background-color: #000;
padding: .25rem;
color: rgb(255 255 255);
}
.carousel-button.carousel-button-start {
left: .75rem;
}
.carousel-button.carousel-button-end {
right: .75rem;
}
JavaScript
window.carousel = () => {
let handleScroll, timer, cleanup;
return {
scrolledStart: false, // Флаг, який каже що контейнер .carousel-items проскролено до лівої межі
scrolledEnd: false, // Флаг, який каже що контейнер .carousel-items проскролено до правої межі
init() {
// Функція, яка викликається щоразу як скорлиться контейнер .carousel-items, відслідковує
// позицію скрола та встановлює флаги scrolledStart, scrolledEnd у відповідні значення
handleScroll = () => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
this.scrolledEnd =
Math.ceil(
this.$refs.items.scrollLeft + this.$refs.items.clientWidth,
) >= Math.ceil(this.$refs.items.scrollWidth);
this.scrolledStart = this.$refs.items.scrollLeft <= 0;
}, 10);
};
handleScroll();
this.$refs.items.addEventListener("scroll", handleScroll, false);
addEventListener("resize", handleScroll, false);
cleanup = () => {
this.$refs.items.removeEventListener("scroll", handleScroll, false);
removeEventListener("resize", handleScroll, false);
};
},
destroy() {
cleanup();
},
// Функція яка скролить контейнер .carousel-items на один слайд вправо (вперед)
next() {
const left =
this.$refs.items.scrollLeft + this.$refs.items.clientWidth >=
Math.ceil(this.$refs.items.scrollWidth)
? this.$refs.items.scrollWidth - this.$refs.items.clientWidth
: this.$refs.items.scrollLeft + this.$refs.items.clientWidth;
this.$refs.items.scrollTo({
top: 0,
left,
behavior: "smooth",
});
},
// Функція яка скролить контейнер .carousel-items на один слайд вліво (назад)
prev() {
const left =
this.$refs.items.scrollLeft - this.$refs.items.clientWidth > 0
? this.$refs.items.scrollLeft - this.$refs.items.clientWidth
...