Carousel.tsx
import React from "react";export default function Carousel() { const ref = React.useRef<HTMLDivElement>(null); const [index, setIndex] = React.useState(0); const [numberOfImages, setNumberOfImages] = React.useState(3); React.useEffect(() => { if (!ref.current) return; const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { if ((entry.target as HTMLImageElement).complete === false) { return; } const index = (entry.target as HTMLImageElement).dataset.index; if (index) { setIndex(Number(index)); } } }); }, { root: ref.current, threshold: 1, } ); const children = ref.current.children; for (let i = 0; i < children.length; i++) { (children[i] as HTMLImageElement).dataset.index = i.toString(); observer.observe(children[i]); } return () => { observer.disconnect(); }; }, [numberOfImages]); return ( <> <section className="relative"> <button onClick={() => ref.current?.scrollBy(-1000, 0)} className="absolute top-1/2 left-0 text-4xl" > ⬅️ </button> <button onClick={() => ref.current?.scrollBy(1000, 0)} className="absolute top-1/2 right-0 text-4xl" > ➡️ </button> <nav className="absolute bottom-8 flex justify-center w-full"> <ul className="flex gap-2 text-7xl text-gray-400 font-thin"> {[...Array(numberOfImages)].map((_, i) => { return ( <li key={i}> <button className={i === index ? "text-white" : ""} onClick={() => { ref.current?.scrollTo({ left: i * 1000, }); }} > _ </button> </li> ); })} </ul> </nav> <div ref={ref} className="scroll-smooth overflow-x-scroll whitespace-nowrap snap-x snap-mandatory" style={{ scrollbarWidth: "none" }} // wait for scrollbarwidth to be supported to hide scrollbar > {[...Array(numberOfImages)].map((_, i) => { return ( <img src="//unsplash.it/1000/400" className="inline-block snap-start" key={i} onLoad={() => ref.current?.scrollTo({ left: 0 })} // images are loaded at different orders, so we need to somehow start at the first image by using this hack /> ); })} </div> </section> <label> Number of Images <input type="range" min={1} max={6} value={numberOfImages} onChange={(e) => setNumberOfImages(Number(e.target.value))} /> </label> </> );}