【React】縦スクロールをすると横にスライドするページを実装する


概要

縦スクロールする(マウスホイールする)と、ページが横に切り替わるコンポーネントを実装します。

スタイリングには、emotion/css(CSS in JS)を使用しています。

実装

実装のイメージ図です。主要なCSSプロパティを記述しています。

import React, { useEffect, useRef, VFC } from 'react';
import { css } from '@emotion/css';

export const ParallaxHorizontalScrollPage: VFC = () => {
    const screenRef = useRef<HTMLDivElement>(null)

    useEffect(() => {
        screenRef.current!.onwheel = ev => {
            ev.preventDefault()

            let delta = (ev.deltaY / Math.abs(ev.deltaY)) * window.innerWidth
            if (delta > 0) {
                delta += screenRef.current!.scrollLeft
                delta = Math.floor(delta / window.innerWidth) * window.innerWidth
            } else {
                delta += screenRef.current!.scrollLeft
                delta = Math.ceil(delta / window.innerWidth) * window.innerWidth
            }
            screenRef.current!.scrollLeft = delta
        }
    }, [])

    return (
        <div ref={screenRef} className={styles.screen}>
            <div className={styles.container}>
                <div className={styles.page('#0085d1')}>
                    <div className={styles.text}>Page 1</div>
                </div>
                <div className={styles.page('#db0063')}>
                    <div className={styles.text}>Page 2</div>
                </div>
                <div className={styles.page('#ffcd43')}>
                    <div className={styles.text}>Page 3</div>
                </div>
            </div>
        </div>
    )
}

const styles = {
    screen: css`
        position: relative;
        width: 100vw;
        height: 100vh;
        overflow: auto;
        scroll-behavior: smooth;
    `,
    container: css`
        width: 300vw;
        height: 100%;
        display: flex;
    `,
    page: (bgColor: string) => css`
        position: relative;
        width: 100vw;
        height: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: ${bgColor};
    `,
    text: css`
        font-size: 3rem;
        color: white;
    `
}

useEffectの中で、screen(CSS名のdiv要素)に対してスクロール量を調整しています。

deltaは横のスクロール量で、1回の縦スクロールで画面幅(innerWidth)だけ横スクロールします。
縦のスクロール量を絶対値で割ることで、+/-を取得しています。

let delta = (ev.deltaY / Math.abs(ev.deltaY)) * window.innerWidth

以下のコードでは、ページを途中まで手動で横スクロールしているときに、縦スクロールをしたときの横のスクロール量を決めています。
これによって、デモ(記事上部のGIF)のような動作になります。

if (delta > 0) {
    delta += screenRef.current!.scrollLeft
    delta = Math.floor(delta / window.innerWidth) * window.innerWidth
} else {
    delta += screenRef.current!.scrollLeft
    delta = Math.ceil(delta / window.innerWidth) * window.innerWidth
}