お好きな金額でサポートしていただけると嬉しいです!
(50円〜10,000円[税込]まで)
作成日時:2025年8月24日 22:19
更新日時:2025年8月28日 21:30
GSAP
React
GSAPのScrollTriggerを使ってを使って横スクロールを実装する方法を、今回はReactで検証してみたので、簡単にまとめます。
GSAPとは、高性能なJavaScriptアニメーションライブラリです。
"gsap": "^3.13.0"
スクロール位置を監視し、以下を制御できるプラグインです。
スクラブ、ピン、スナップなど、スクロールに関連するあらゆるものをトリガーさせることができます。
scrub は「スクロール位置とアニメ進行をどの程度リアルタイム同期させるか」を指定するオプションです。
これを利用することでよく見かけるような、スクロールに連動した滑らかなアニメーション表現が実現できます。
今回はこれを使って、縦スクロールをすると 100vw 幅のセクション群が横にスライドし、ページは「横スクロールしている」ように見える、というものを実装してみましょう。
まずインストールしましょう。
npm i gsap
import React, { useLayoutEffect, useRef } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import styles from "./SamplePage.module.css";
gsap.registerPlugin(ScrollTrigger);
const SamplePage: React.FC = () => {
const wrapperRef = useRef<HTMLDivElement>(null);
const pinRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
const wrap = wrapperRef.current;
const pinEl = pinRef.current;
if (!wrap || !pinEl) return;
const sections = Array.from(wrap.children) as HTMLElement[];
const total = sections.length - 1;
const ctx = gsap.context(() => {
gsap.to(sections, {
xPercent: -100 * total, // (枚数-1) * -100%
ease: "none",
scrollTrigger: {
trigger: pinEl,
pin: true,
scrub: 1, // 数値調整で滑らかさを調整する
end: () => "+=" + (wrap.scrollWidth - window.innerWidth),
anticipatePin: 1,
invalidateOnRefresh: true,
},
});
});
return () => ctx.revert();
}, []);
return (
<main className={styles.main}>
<div ref={pinRef} className={styles.pinArea}>
<div ref={wrapperRef} className={styles.horizontal}>
<section className={styles.section}>
<div className={styles.container}>
<h2 className={styles.subtitle}>サブタイトル 1</h2>
<p className={styles.text}>最初のセクションです</p>
</div>
</section>
<section className={styles.section}>
<div className={styles.container}>
<h2 className={styles.subtitle}>サブタイトル 2</h2>
<p className={styles.text}>2番目のセクションです</p>
</div>
</section>
<section className={styles.section}>
<div className={styles.container}>
<h2 className={styles.subtitle}>サブタイトル 3</h2>
<p className={styles.text}>3番目のセクションです</p>
</div>
</section>
</div>
</div>
</main>
);
};
export default SamplePage;
.main {
overflow-x: hidden;
}
.pinArea {
position: relative;
height: 100vh;
width: 100%;
}
.horizontal {
display: flex;
height: 100%;
}
.section {
flex: 0 0 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.container {
text-align: center;
max-width: 640px;
padding: 2rem;
}
.subtitle,
.text {
color: #fff;
}
.subtitle {
font-size: 2.4rem;
margin-bottom: 1rem;
}
.text {
font-size: 1.1rem;
line-height: 1.6;
}
.section:nth-child(1) {
background: #1e3a8a;
}
.section:nth-child(2) {
background: #9333ea;
}
.section:nth-child(3) {
background: #047857;
}
ちなみに、snapを使うと、進行度を分割した最も近い位置に吸着させる、なんてこともできます。
(使わなければ普通の自由スクロールになる)
症状 | 原因 | 解決 |
---|---|---|
最後まで横に行かない | end 固定/短い | end を関数 + scrollWidth 使用 |
途中で止まる | 親に overflow:hidden でスペーサー生成不可 | 親(スクロールコンテキスト)の縦方向 overflow を許可 |
レイアウト崩れ | flex 指定抜け | .horizontal{display:flex} と .section{flex:0 0 100vw} |
カクつく | 画像読み込み遅延 | 画像に width/height 指定、scrub 数値調整 |
今回はやっていませんが、他にもいろいろとできちゃえるので、興味のある方は下記の公式デモを参考に、使ってみたいものを実装してみるとよいかと思います。
それでは、また。
HTNCode