Basic
Dots
Add interactive dot markers with IntersectionObserver.
1
2
3
4
5
6
7
8
9
10
const slides = document.querySelectorAll(".slide");
const dots = document.querySelectorAll(".dot");
let activeIndex = 0;
const intersectionRatioBySlide = new WeakMap<Element, number>();
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
intersectionRatioBySlide.set(
entry.target,
entry.isIntersecting ? entry.intersectionRatio : 0,
);
}
const mostVisible = Array.from(slides).reduce(
(current, slide, index) => {
const ratio = intersectionRatioBySlide.get(slide) ?? 0;
return ratio > current.ratio ? { index, ratio } : current;
},
{ index: activeIndex, ratio: 0 },
);
if (mostVisible.ratio > 0) {
activeIndex = mostVisible.index;
dots.forEach((dot, i) => dot.classList.toggle("active", i === activeIndex));
}
},
{ threshold: [0, 0.5, 1] },
);
slides.forEach((slide) => observer.observe(slide));
dots.forEach((dot, index) => {
dot.addEventListener("click", () => {
slides[index]?.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "center",
});
});
});
.carousel {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 100%;
gap: 1rem;
scroll-snap-type: x mandatory;
height: 22.5rem;
}
.slide {
width: 100%;
scroll-snap-align: center;
}
.dots {
display: flex;
gap: 0.25rem;
}
.dot {
opacity: 0.4;
}
.dot.active {
opacity: 1;
}
<BlossomCarousel class="carousel">
<div class="slide"></div>
<div class="slide"></div>
<div class="slide"></div>
...
</BlossomCarousel>
<div class="dots">
<button class="dot"></button>
<button class="dot"></button>
<button class="dot"></button>
...
</div>