🐙

shadcn/uiで自動でスライドするCarouselを作ろう

2024/04/12に公開2

はじめに

  • みなさんshadcn/uiを使ってますか?知らない方のために説明すると、RadixUIとtailwindcssをベースに作られたコンポーネントで、シンプルでかっこいいUIを簡単に作ることができます!この記事では、その中でもCarouselというコンポーネントを改良する方法について紹介したいと思います。

Carouselとは

  • Carouselとは、複数のコンテンツをスライドして左右に切り替えることができるコンポーネントです。ECサイトでキャンペーンなどの広告によく使われている印象です。

  • 簡単に説明すると、CarouselContentには表示させたい要素を置き、CarouselPreviousとCarouselNextでページ遷移をする感じです。ちなみに、スマホでは要素を直接スライドすることでページ遷移することもできます。

<Carousel>
  <CarouselContent>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
</Carousel>

自動スライド対応させる

  • Carouselのプログラムファイルは、components/uiディレクトリにあるcarousel.tsxになります。このファイルを直接いじることで自動スライド対応にさせていきたいと思います。

  • 以下がcomponents/ui/carousel.tsxのCarouselコンポーネントのプログラムになります。ここに数行変更を加えていきます。

Carousel
const Carousel = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & CarouselProps>(({ orientation = "horizontal", opts, setApi, plugins, className, children, ...props }, ref) => {
    const [carouselRef, api] = useEmblaCarousel(
        {
            ...opts,
            axis: orientation === "horizontal" ? "x" : "y",
        },
        plugins
    );
    const [canScrollPrev, setCanScrollPrev] = React.useState(false);
    const [canScrollNext, setCanScrollNext] = React.useState(false);

    const onSelect = React.useCallback((api: CarouselApi) => {
        if (!api) {
            return;
        }

        setCanScrollPrev(api.canScrollPrev());
        setCanScrollNext(api.canScrollNext());
    }, []);

    const scrollPrev = React.useCallback(() => {
        api?.scrollPrev();
    }, [api]);

    const scrollNext = React.useCallback(() => {
        api?.scrollNext();
    }, [api]);

    const handleKeyDown = React.useCallback(
        (event: React.KeyboardEvent<HTMLDivElement>) => {
            if (event.key === "ArrowLeft") {
                event.preventDefault();
                scrollPrev();
            } else if (event.key === "ArrowRight") {
                event.preventDefault();
                scrollNext();
            }
        },
        [scrollPrev, scrollNext]
    );

    React.useEffect(() => {
        if (!api) {
            return;
        }

        onSelect(api);
        api.on("reInit", onSelect);
        api.on("select", onSelect);

        return () => {
            api?.off("select", onSelect);
        };
    }, [api, onSelect]);

    return (
        <CarouselContext.Provider
            value={{
                carouselRef,
                api: api,
                opts,
                orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
                scrollPrev,
                scrollNext,
                canScrollPrev,
                canScrollNext,
            }}
        >
            <div ref={ref} onKeyDownCapture={handleKeyDown} className={cn("relative", className)} role="region" aria-roledescription="carousel" {...props}>
                {children}
            </div>
        </CarouselContext.Provider>
    );
});
Carousel.displayName = "Carousel";
  1. まず、Carouselがレンダーされる際に自動スライドプログラムを実行させるために、useEffectを使う必要があります。この際に第二引数にscrollNextを指定することを忘れないでください。これがなければ自動スライドされません。
React.useEffect(() => {
}, [scrollNext]);
  1. つづいて、自動スライドさせるプログラムをuseEffectの中に追加指定いきます。setInterval関数を使って一定の間隔ごとにスライドされるようにします。また、要素が最後までスクロールされたら一番最初に戻るようにも設定しておきます
const timer = setInterval(() => {
    if (api && !api.canScrollNext()) {
        api.scrollTo(0);
    } else {
        scrollNext();
    }
}, 2000); // Adjust the interval as needed (in milliseconds)

return () => {
    clearInterval(timer);
};
  1. 最終的には以下のようになります
React.useEffect(() => {
    const timer = setInterval(() => {
        if (api && !api.canScrollNext()) {
            api.scrollTo(0);
        } else {
            scrollNext();
        }
    }, 2000); // Adjust the interval as needed (in milliseconds)

    return () => {
        clearInterval(timer);
    };
}, [scrollNext]);
  1. 完成
完成プログラム
const Carousel = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & CarouselProps>(({ orientation = "horizontal", opts, setApi, plugins, className, children, ...props }, ref) => {
    const [carouselRef, api] = useEmblaCarousel(
        {
            ...opts,
            axis: orientation === "horizontal" ? "x" : "y",
        },
        plugins
    );
    const [canScrollPrev, setCanScrollPrev] = React.useState(false);
    const [canScrollNext, setCanScrollNext] = React.useState(false);

    const onSelect = React.useCallback((api: CarouselApi) => {
        if (!api) {
            return;
        }

        setCanScrollPrev(api.canScrollPrev());
        setCanScrollNext(api.canScrollNext());
    }, []);

    const scrollPrev = React.useCallback(() => {
        api?.scrollPrev();
    }, [api]);

    const scrollNext = React.useCallback(() => {
        api?.scrollNext();
    }, [api]);

    const handleKeyDown = React.useCallback(
        (event: React.KeyboardEvent<HTMLDivElement>) => {
            if (event.key === "ArrowLeft") {
                event.preventDefault();
                scrollPrev();
            } else if (event.key === "ArrowRight") {
                event.preventDefault();
                scrollNext();
            }
        },
        [scrollPrev, scrollNext]
    );

    React.useEffect(() => {
        const timer = setInterval(() => {
            if (api && !api.canScrollNext()) {
                api.scrollTo(0);
            } else {
                scrollNext();
            }
        }, 2000); // Adjust the interval as needed (in milliseconds)

        return () => {
            clearInterval(timer);
        };
    }, [scrollNext]);

    React.useEffect(() => {
        if (!api) {
            return;
        }

        onSelect(api);
        api.on("reInit", onSelect);
        api.on("select", onSelect);

        return () => {
            api?.off("select", onSelect);
        };
    }, [api, onSelect]);

    return (
        <CarouselContext.Provider
            value={{
                carouselRef,
                api: api,
                opts,
                orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
                scrollPrev,
                scrollNext,
                canScrollPrev,
                canScrollNext,
            }}
        >
            <div ref={ref} onKeyDownCapture={handleKeyDown} className={cn("relative", className)} role="region" aria-roledescription="carousel" {...props}>
                {children}
            </div>
        </CarouselContext.Provider>
    );
});
Carousel.displayName = "Carousel";

最後に

  • いかがでしたでしょうか。結構簡単に実装できたと思います。やっぱりshadcn/uiは直接プログラムをいじれるので自由度が高くて楽しいですね。

Discussion