Skip to content

Instantly share code, notes, and snippets.

@nothingnesses
Created February 5, 2024 17:12
Show Gist options
  • Save nothingnesses/f64555fa1890064cc51ae64fa52cddca to your computer and use it in GitHub Desktop.
Save nothingnesses/f64555fa1890064cc51ae64fa52cddca to your computer and use it in GitHub Desktop.
Carousel based on scroll snap

simple carousel

This was supposed to be a simple and performant solution to the carousel problem that uses the native scroll-snap-type and scroll-snap-align styles to achieve the effect, but I abandoned the attempt after realising that it can't be used for carousels that loop.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.jsdelivr.net/npm/@unocss/runtime"></script>
</head>
<body>
<main class="h-screen w-screen bg-white grid items-center justify-items-center">
<div class="carousel-container w-full h-full grid items-center justify-items-center">
<div class="carousel snap-x snap-mandatory [&>*]:snap-start overflow-x-scroll [&::-webkit-scrollbar]:hidden bg-gray h-1/2 max-w-1/2 w-full items-center" style="scrollbar-width: none;">
<div class="h-2/3 grid items-center justify-items-center bg-red">1</div>
<div class="h-2/3 grid items-center justify-items-center bg-green">2</div>
<div class="h-2/3 grid items-center justify-items-center bg-blue">3</div>
<div class="h-2/3 grid items-center justify-items-center bg-red">4</div>
<div class="h-2/3 grid items-center justify-items-center bg-green">5</div>
<div class="h-2/3 grid items-center justify-items-center bg-blue">6</div>
<div class="h-2/3 grid items-center justify-items-center bg-red">7</div>
<div class="h-2/3 grid items-center justify-items-center bg-green">8</div>
<div class="h-2/3 grid items-center justify-items-center bg-blue">9</div>
</div>
<div class="carousel-pagination"></div>
<div class="carousel-next"></div>
<div class="carousel-previous"></div>
</div>
</main>
<script src="script.js"></script>
</body>
</html>
(({
carousel_class = "carousel",
container_class = "carousel-container",
pagination_class = "carousel-pagination",
pagination_bullet_element = "div",
pagination_bullet_class = "carousel-bullet",
pagination_bullet_class_active = "carousel-bullet-active",
previous_class = "carousel-previous",
next_class = "carousel-next",
momentum_decay = 0.95,
momentum_threshold = 0.5,
slides_per_view = 1,
}) => {
const class_to_selector = (cls) => `.${cls}`;
document.querySelectorAll(class_to_selector(container_class)).forEach((container) => {
const carousel = container.querySelector(class_to_selector(carousel_class));
if (carousel) {
// Handle mouse device drag events
if (
!(
"ontouchstart" in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0
)
) {
let momentum_frame_id = 0;
const stop_momentum = () => {
if (momentum_frame_id) {
cancelAnimationFrame(momentum_frame_id);
}
};
carousel.addEventListener("mousedown", (event) => {
const client_x = event.clientX;
const scroll_left = carousel.scrollLeft;
let movement_x = 0;
const scroll_carousel = (event) => {
carousel.scrollLeft = scroll_left - (event.clientX - client_x);
movement_x = event.movementX;
};
const momentum_worker = () => {
carousel.scrollLeft -= movement_x;
movement_x *= momentum_decay;
if (Math.abs(movement_x) > momentum_threshold) {
momentum_frame_id = window.requestAnimationFrame(momentum_worker);
} else {
carousel.classList.add("snap-x");
}
};
const stop_scroll = () => {
document.removeEventListener("mousemove", scroll_carousel);
document.removeEventListener("mouseup", stop_scroll);
document.removeEventListener("dragstart", stop_scroll);
stop_momentum();
momentum_worker();
};
carousel.classList.remove("snap-x");
stop_momentum();
document.addEventListener("dragstart", stop_scroll);
document.addEventListener("mouseup", stop_scroll);
document.addEventListener("mousemove", scroll_carousel);
});
}
// Give a min width to slides based on carousel
(() => {
carousel.style.display = "flex";
const worker = (carousel_width) => {
const slide_width = `${carousel_width / slides_per_view}px`;
for (const slide of carousel.children) {
slide.style.minWidth = slide_width;
}
};
worker(carousel.offsetWidth);
// Handle resize
new ResizeObserver((entries) =>
entries.forEach((entry) => worker(entry.contentRect.width))
).observe(carousel);
})();
// Handle pagination
const pagination = container.querySelector(class_to_selector(pagination_class));
if (pagination) {
for (let index = 0; index < carousel.children.length; ++index) {
const bullet = document.createElement(pagination_bullet_element);
bullet.classList.add(pagination_bullet_class);
pagination.appendChild(bullet);
}
if (pagination.children.length > 0) {
pagination.children[1].classList.add(pagination_bullet_class_active);
}
/** @todo */
}
// Handle navigation
const previous_button = container.querySelector(class_to_selector(previous_class));
if (previous_button) {
/** @todo */
}
const next_button = container.querySelector(class_to_selector(next_class));
if (next_button) {
/** @todo */
}
}
});
})({});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment