複数要素を表示させつつ、縦スクロールで1要素ずつスライドするページを作る
初めに...
こちらの記事だとトラックパッドを使った時の挙動がかなり違和感があります。新たに記事を書いたのでこちらをご参照ください。
やりたいこと
- 複数要素を画面に表示していたい
- マウスの縦スクロールで1スライドずつ送りたい
完成品
見づらいので「open in new window」をクリックするなどして見てください
実装について
コードの全体像
scrollableScreen.tsx
export const ScrollableScreen = (props: ScrollableScreenProps) => {
const { index, visibleScreenItemNum = 4, screenItemNum = 8 } = props;
const screenWindowRef = useRef<HTMLDivElement>(null);
const [scrollLeftIndex, setScrollLeftIndex] = useState(0);
const screenSize = 250;
const screenGap = 4;
const screenWindowWidth =
screenSize * visibleScreenItemNum + screenGap * (visibleScreenItemNum - 1);
const screenWrapperWidth =
screenSize * screenItemNum + screenGap * (screenItemNum - 1);
const scrollWidth = screenSize + screenGap;
useEffect(() => {
if (!screenWindowRef.current) return;
screenWindowRef.current.onwheel = (e) => {
if (!screenWindowRef.current) return;
e.preventDefault();
let delta = e.deltaY / Math.abs(e.deltaY);
if (delta > 0) {
setScrollLeftIndex((prev) => {
console.log(prev);
if (prev === screenItemNum - visibleScreenItemNum) return prev;
return prev + 1;
});
} else {
setScrollLeftIndex((prev) => {
console.log(prev);
if (prev === 0) return prev;
return prev - 1;
});
}
screenWindowRef.current!.scrollLeft = scrollLeftIndex * scrollWidth;
};
}, [scrollLeftIndex, scrollWidth]);
const indexArray: number[] = Array.from(
{ length: screenItemNum },
(_, index) => index + 1
);
return (
<div className={styles.scrollable_screen_field}>
<p className={styles.scrollable_screen_index}>{index}</p>
<div
className={styles.scrollable_window}
style={{ width: screenWindowWidth }}
ref={screenWindowRef}
>
<div
className={styles.scrollable_screen_wrapper}
style={{ width: screenWrapperWidth, gap: screenGap }}
>
{indexArray.map((i) => (
<div
key={i}
className={styles.scrollable_screen}
style={{
width: screenSize,
height: screenSize
}}
>
<p className={styles.display_index}>{i}</p>
</div>
))}
</div>
</div>
</div>
);
};
scrollableScreen.module.scss
.scrollable_screen_field {
display: flex;
flex-direction: column;
gap: 40px;
align-items: center;
justify-content: center;
width: 100%;
margin: 80px 0;
.scrollable_screen_index {
color: black;
font-size: 24px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.scrollable_window {
overflow: auto;
scroll-behavior: smooth;
&::-webkit-scrollbar {
display: none;
}
.scrollable_screen_wrapper {
display: flex;
.scrollable_screen {
display: flex;
align-items: center;
justify-content: center;
background-color: gray;
.display_index {
font-size: xx-large;
color: white;
}
}
}
}
}
スクロール部分について(tsx側)
以降それぞれのdivタグは次のように呼ぶ
- window:
className={styles.scrollable_window}
のdivタグ - wrapper:
className={styles.scrollable_screen_wrapper}
のdivタグ - screen:
className={styles.scrollable_screen}
のdivタグ
const screenWindowRef = useRef<HTMLDivElement>(null);
const [scrollLeftIndex, setScrollLeftIndex] = useState(0);
const screenSize = 250;
const screenGap = 4;
const screenWindowWidth =
screenSize * visibleScreenItemNum + screenGap * (visibleScreenItemNum - 1);
const screenWrapperWidth =
screenSize * screenItemNum + screenGap * (screenItemNum - 1);
const scrollWidth = screenSize + screenGap;
定数やstateの定義
scrollLeftIndex
のstateは[今一番左側に表示されているボックスに書かれた数字 - 1]を表現している
screenWindowWidth
, screenWrapperWidth
はそれぞれwindowとwrapperの幅を表している
scrollWidth
はscrollLeftIndex
が1増加した時に要素がどれだけ動くかを表している
useEffect(() => {
if (!screenWindowRef.current) return;
screenWindowRef.current.onwheel = (e) => {
if (!screenWindowRef.current) return;
e.preventDefault();
let delta = e.deltaY / Math.abs(e.deltaY);
if (delta > 0) {
setScrollLeftIndex((prev) => {
console.log(prev);
if (prev === screenItemNum - visibleScreenItemNum) return prev;
return prev + 1;
});
} else {
setScrollLeftIndex((prev) => {
console.log(prev);
if (prev === 0) return prev;
return prev - 1;
});
}
screenWindowRef.current!.scrollLeft = scrollLeftIndex * scrollWidth;
};
}, [scrollLeftIndex, scrollWidth]);
全体としてはマウスホイールのスクロールが検知された時にその方向に応じてscrollLeftIndex
を+1 or -1させ、scrollLeftIndex
を参照してscreenWindowRef.current.scrollLeft
に現在のスクロール位置(scrollLeftIndex * scrollWidth
)を代入している。
わかりづらそうな(自分がつまづいた)ポイントに分けてリンクを貼っておく
refがわからない/ref.currentってなんだ
e.preventDefault()ってなぜ必要なんだ
scrollLeftって具体的に何をしてるんだ
スクロール部分について(scss側)
.scrollable_window {
overflow: auto;
scroll-behavior: smooth;
&::-webkit-scrollbar {
display: none;
}
.scrollable_screen_wrapper {
display: flex;
// 略
}
}
window
overflow: auto
を指定することでスクロールを可能かつ要素の外側に出た子要素を見えないようにしている
scroll-behavior: smooth
を指定することでスクロールした時に次のスライドが見えるまで、スライドが滑らかに動くようにしている
&::-webkit-scrollbar {
display: none;
}
スクロールバーがダサいので隠している
wrapper
子要素を等間隔横並びにするためにdisplay: flex
を使用
課題
現状トラックパッドで非常に操作しづらくなっているため, 何かしらの対策を考える必要がある
参考リンク
Discussion