📜

React 18(Next.js)でGSAPする

2022/10/09に公開

環境

  • yarn create next-app --typescript で生成
  • Next.js 12.3.1
  • react 18.2.0
  • gsap 3.11.3

ざっくり解説

  • 完成品をもとに抜き出して解説していきます

GSAPの準備

import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/dist/ScrollTrigger'
gsap.registerPlugin(ScrollTrigger)
  • 使う機能をregisterPluginしておく
    • 今回は『スクロールで発火する処理』を作るので、ScrollTriggerをいれておく
  • React内では/dist/つきのパスを読み込む必要がある

GSAPをhookするためのrefを作成する

  • setupGsapはGSAPの設定のための関数。次で解説する。
    • GSAPが必要とするのはcurrentの中身の方なので、currentの中身だけ取り出して渡しておくと良い
  • React18からデフォルトでマウントが2回実施されるようになったが、GSAPのセットアップは1回でないと困るのでdidEffectのようにして1回にする。
const pagesWrapperRef = useRef<HTMLDivElement | null >(null);
const pagesRef = useRef<HTMLDivElement | null >(null);
const didEffect = React.useRef(false);

useEffect(() => {
  if (didEffect.current) return 
  didEffect.current = true;
  const pagesElement = pagesRef?.current;
  if(!pagesElement) return
  const pagesWrapperElement = pagesWrapperRef?.current;
  if(!pagesWrapperElement) return
  setupGsap(pagesElement, pagesWrapperElement)
}, [])

縦スクロールを横スクロールにするGSAPを設定する

  • setupGSapという関数にまとめておく
    • 関数化してるのは趣味なのでuseEffectにべた書きでも問題ない。お好みでどうぞ。
const setupGsap = (pagesElement: HTMLDivElement, pagesWrapperElement: HTMLDivElement) => {
  gsap.to(pagesElement, {
    x: () => -(pagesElement.clientWidth - pagesWrapperElement.clientWidth),
    ease: 'none',
    scrollTrigger: {
      trigger: '#horizontal-scroll-section',
      start: 'top top',
      end: () => `+=${pagesElement.clientWidth - pagesWrapperElement.clientWidth}`,
      scrub: true,
      pin: true,
      anticipatePin: 1,
      invalidateOnRefresh: true,
    },
  })
}
<main className={styles.main}>
  <section className={styles.section} id="horizontal-scroll-section">
    <div className={styles.container}>
      <div className={styles.pagesWrapper} ref={pagesWrapperRef}>
        <div className={styles.pages} ref={pagesRef} id="pages">
          {IMAGE_LIST.map((data) => {
            return (<Page key={data.id} srcpath={createPublicImagePath(data.id.toString())}/>)
          })}
          <PageLast/>
        </div>
      </div>
    </div>
  </section>
</main>
.main {
  background: black;
}

.section {
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}

.container {
  width: 100%;
  height: 100vh;
}

.pagesWrapper {
  position: relative;
  height: 100vh;
}

.pages {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  height: 100%;
  justify-content: center;
  align-items: center;
}

【おまけ】スクロールして一定の場所を超えたら背景色を変更する

  • 本来の目的である『縦スクロールしたら横スクロールする』機能はすでに完成していますが、映画館のごとく最後に明るくしたかったので、もう少し書きます
  • setupGsapに下記を仲間入りさせます
gsap.to('main', {
  scrollTrigger: {
    trigger: '#horizontal-scroll-section',
    start: 'top center',
    end: () => `+=${pagesElement.clientWidth - pagesWrapperElement.clientWidth + 200}`,
    scrub: true, 
    onEnter: () => gsap.to('main', {
      backgroundColor: '#000',
      duration: 1.4
    }),
    onLeave: () => gsap.to('main', {
      backgroundColor: '#fff',
      duration: 1.4
    }),
    onEnterBack: () => gsap.to('main', {
      backgroundColor: '#000',
      duration: 1.4
    }),
    onLeaveBack: () => gsap.to('main', {
      backgroundColor: '#fff',
      duration: 1.4
    }),
  },
})
  • 縦スクロールを横スクロールに変化させる機能のGSAPを少し魔改造したもの
  • onEnter, onLeave, onEnterBack, onLeaveBackは名の通り。Backを追加しなければ『通過時一度だけ』な変化が作れる。
  • 背景色はもともと黒。start: 'top top'にするとonEnterが発火せず、onLeaveも発火しないので、start: 'top center'にする

おわり

  • 集大成としてできたのがこれ
  • React + GSAPの事例がすくなくて動くまでが山場だった
    • useEffectが2回マウントは、普段React17なのもあって気づくのがかなり遅れた
  • 動いてからはとても楽しかったのでもう少しGSAPに触れてみようと思う

Discussion