ui · carousel
Défilement de slides avec flèches et puces de navigation. Utilise le scroll-snap natif du navigateur (fluide, tactile sur mobile), piloté par un peu de JavaScript pour les flèches/puces.
<style>
.bn-carousel {
position: relative;
font-family: 'Inter', sans-serif;
background: #0B0F10;
padding: 1rem;
}
.bn-carousel__track {
display: flex;
overflow-x: auto;
/* Le scroll-snap natif fait le plus gros du travail : chaque
slide "s'accroche" proprement au bord au lieu de s'arrêter
n'importe où pendant le défilement */
scroll-snap-type: x mandatory;
scrollbar-width: none;
border-radius: 6px;
}
.bn-carousel__track::-webkit-scrollbar {
display: none;
}
.bn-carousel__slide {
flex: 0 0 100%;
scroll-snap-align: start;
height: 260px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #1D2324, #0B0F10);
color: #E6E4DD;
font-family: 'Bebas Neue', sans-serif;
font-size: 2rem;
}
.bn-carousel__arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 40px;
height: 40px;
border-radius: 50%;
border: 1px solid rgba(230, 228, 221, 0.3);
background: rgba(5, 8, 8, 0.6);
color: #E6E4DD;
cursor: pointer;
}
.bn-carousel__arrow:hover {
border-color: #E6E4DD;
}
.bn-carousel__arrow--prev { left: 1.5rem; }
.bn-carousel__arrow--next { right: 1.5rem; }
.bn-carousel__dots {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-top: 1rem;
}
.bn-carousel__dot {
width: 7px;
height: 7px;
border-radius: 50%;
border: none;
background: rgba(230, 228, 221, 0.25);
cursor: pointer;
transition: background 0.2s ease, transform 0.2s ease;
}
.bn-carousel__dot.is-active {
background: #E6E4DD;
transform: scale(1.3);
}
</style>
<div class="bn-carousel" data-fx-root>
<div class="bn-carousel__track">
<div class="bn-carousel__slide"><span>1</span></div>
<div class="bn-carousel__slide"><span>2</span></div>
<div class="bn-carousel__slide"><span>3</span></div>
<div class="bn-carousel__slide"><span>4</span></div>
</div>
<button type="button" class="bn-carousel__arrow bn-carousel__arrow--prev" aria-label="Précédent">←</button>
<button type="button" class="bn-carousel__arrow bn-carousel__arrow--next" aria-label="Suivant">→</button>
<div class="bn-carousel__dots">
<button type="button" class="bn-carousel__dot is-active" data-index="0" aria-label="Aller à la slide 1"></button>
<button type="button" class="bn-carousel__dot" data-index="1" aria-label="Aller à la slide 2"></button>
<button type="button" class="bn-carousel__dot" data-index="2" aria-label="Aller à la slide 3"></button>
<button type="button" class="bn-carousel__dot" data-index="3" aria-label="Aller à la slide 4"></button>
</div>
</div>
<script>
/* ==========================================================
CAROUSEL
Les flèches font défiler d'une slide à la fois. Les puces
sautent directement à la slide voulue. La puce active se met à
jour automatiquement en fonction de la position du scroll — même
si l'utilisateur fait défiler à la souris ou au doigt sans
toucher aux boutons.
========================================================== */
(function () {
const root = document.querySelector('.bn-carousel[data-fx-root]');
if (!root) return;
const track = root.querySelector('.bn-carousel__track');
const slides = root.querySelectorAll('.bn-carousel__slide');
const dots = root.querySelectorAll('.bn-carousel__dot');
const prevBtn = root.querySelector('.bn-carousel__arrow--prev');
const nextBtn = root.querySelector('.bn-carousel__arrow--next');
function goTo(index) {
const clamped = Math.max(0, Math.min(index, slides.length - 1));
track.scrollTo({ left: slides[clamped].offsetLeft, behavior: 'smooth' });
}
function currentIndex() {
return Math.round(track.scrollLeft / track.clientWidth);
}
prevBtn.addEventListener('click', () => goTo(currentIndex() - 1));
nextBtn.addEventListener('click', () => goTo(currentIndex() + 1));
dots.forEach((dot) => {
dot.addEventListener('click', () => goTo(Number(dot.dataset.index)));
});
// Met à jour la puce active pendant le scroll (avec un léger
// throttle via requestAnimationFrame pour rester fluide)
let ticking = false;
track.addEventListener('scroll', () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const index = currentIndex();
dots.forEach((dot, i) => dot.classList.toggle('is-active', i === index));
ticking = false;
});
});
})();
</script>