概要
webサイトにありがちな,一定時間で横方向に画像をスクロールするコンポーネントを作ります。
完成品
完成したコードを先に載せておきます。
基本は以下の3つです。
- 複数の画像を横に並べた要素を作る
- 画像のインデックス
scrollIndex
を状態変数として定義する scrollIndex
が更新されたタイミングで,1.のスクロール位置を変更する
加えて,以下の3つの工夫を追加しています。
5. 画面の横幅を動的に取得する
6. 一定時間ごとにscrollIndex
をインクリメントする
import React, { useState, useRef, Component, useEffect } from 'react';
const ImageSlider = () => {
const images: { id: number; text: string; src: string }[] = [
{ id: 0, text: '画像1', src: '/assets/blog/title/isometric.png' },
{ id: 1, text: '画像2', src: '/assets/blog/title/isometric_rear.png' },
{ id: 2, text: '画像3', src: '/assets/blog/title/isometric_under.png' },
];
const [scrollIndex, setScrollIndex] = useState<number>(0);
const [figSize, setFigSize] = useState<number>(50)
const containerRef = useRef<HTMLDivElement>();
/* 画面の横幅を動的に取得する
*/
useEffect(() => {
// divタグに埋め込んだrefから横幅を取得する
const handleResize = () => {
if (containerRef.current) {
setFigSize(containerRef.current.offsetWidth);
}
};
handleResize();
// ウィンドウのサイズが変化するたびにhandleResize関数を再実行
window.addEventListener('resize', handleResize);
return () => {
// アンマウント時に無効化
window.removeEventListener('resize', handleResize);
};
}, []);
/* 所定時間ごとに画像インデックスをインクリメントする
*/
const scrollInterval: number = 7; // 切り替え秒数を設定
useEffect(() => {
const interval = setInterval(() => {
setScrollIndex((e) => (e + 1) % images.length);
}, scrollInterval * 1000);
return () => {
clearInterval(interval);
};
}, []);
/* 画面の横幅,または画像インデックスが変わったタイミングで,スクロール位置を調整する
*/
useEffect(() => {
containerRef.current.scrollTo({
left: scrollIndex * figSize,
behavior: 'smooth',
});
}, [figSize, scrollIndex]);
/* jsx
*/
return (
<div>
<div ref={containerRef} className='flex overflow-x-hidden'>
{images.map((image) => (
<img key={image.id} src={image.src} alt={image.text}></img>
))}
</div>
<div className="flex items-center justify-center">
{images.map((image, index) => (
<button key={image.id} onClick={() => setScrollIndex(index)}>
{index == scrollIndex ? "〇" : "・"}
</button>
))}
</div>
</div >
);
};
export default ImageSlider;
ポイント
1. 複数の画像を横に並べた要素を作る
/* jsx
*/
return (
<div>
<div ref={containerRef} className='flex overflow-x-hidden'>
{images.map((image) => (
<img key={image.id} src={image.src} alt={image.text}></img>
))}
</div>
<div className="flex items-center justify-center">
{images.map((image, index) => (
<button key={image.id} onClick={() => setScrollIndex(index)}>
{index == scrollIndex ? "〇" : "・"}
</button>
))}
</div>
</div >
);
まずは変数images
をmapして,画像を横並びに(flex
)配置します。このままだと画像が横に延々と並んでしまうので,overflow-x-hidden
をつけて隠しましょう。
その下に,切り替えのためのボタンを配置し,各ボタンに後述のsetScrollIndex
関数を割り当てます。
scrollIndex
を状態変数として定義する
2. 画像のインデックス const [scrollIndex, setScrollIndex] = useState<number>(0);
useStateで,scrollIndex
を定義します。
つまり,切り替えボタンを押すとsetScrollIndex
関数が発火し,それぞれのボタンに対応したindex番号が状態変数scrollIndex
に格納されます。
scrollIndex
が更新されたタイミングで,1.のスクロール位置を変更する
3. const [figSize, setFigSize] = useState<number>(500) //とりあえず500pxでスクロールする
const containerRef = useRef<HTMLDivElement>();
....
/* 画面の横幅,または画像インデックスが変わったタイミングで,スクロール位置を調整する
*/
useEffect(() => {
containerRef.current.scrollTo({
left: scrollIndex * figSize,
behavior: 'smooth',
});
}, [/*figSize*/, scrollIndex]);
useRefを使ってcontainerRef
という参照をjsxに埋め込みます(jsxの<div ref={containerRef}
の部分と対応)。
useEffectによって,状態変数scrollIndex
が変更されたときcontainerRef
のスクロール位置を移動させるようにします。
ここまでで,ボタンを押して画像をスクロールするコンポーネントが作られました,
ですが,画像に対して中途半端な位置にスクロールしてしまうと思います。これは上記のfigSize
が500pxで固定されているためです。
JSXのCSSタグで画像をfigSize
と同サイズに揃えることもできるのですが,コンポーネントのサイズが変わるたびに再定義する必要が出てきます。
これを回避するために次の工夫を行います。
4. 画面の横幅を動的に取得する
/* 画面の横幅を動的に取得する
*/
useEffect(() => {
// divタグに埋め込んだrefから横幅を取得する
const handleResize = () => {
if (containerRef.current) {
setFigSize(containerRef.current.offsetWidth);
}
};
handleResize();
// ウィンドウのサイズが変化するたびにhandleResize関数を再実行
window.addEventListener('resize', handleResize);
return () => {
// アンマウント時に無効化
window.removeEventListener('resize', handleResize);
};
}, []);
先ほど参照を定義したcontainerRef
から,動的に要素の横幅(containerRef.current.offsetWidth
)を取得するようにします。
scrollIndex
をインクリメントする
5. 一定時間ごとに /* 所定時間ごとに画像インデックスをインクリメントする
*/
const scrollInterval: number = 7; // 切り替え秒数を設定
useEffect(() => {
const interval = setInterval(() => {
setScrollIndex((e) => (e + 1) % images.length);
}, scrollInterval * 1000);
return () => {
clearInterval(interval);
};
}, []);
仕上げとして,固定の秒数でscrollIndex
をインクリメントします。
最後の画像まで到達すると最初の画像に戻るようにしています。
まとめ
ありがちなスクロールオブジェクトを作りました。
実は,自前実装しなくても,react-horizontal-scrolling-menu[https://www.npmjs.com/package/react-horizontal-scrolling-menu]で実現できるということに後々気が付いてしまいました…
といってもuseEffect, useStateなどの基本的なhooksの使い方が学べたので良しとします。
普段Pythonでデータの処理系ばかり書いていると,webアプリのような動的なプログラミングはなかなか難しいのですが,触ってみると面白いですね。