はじめに
フロントエンドエンジニア歴3年目、現在は80&Companyでフロントエンドを担当しています。
Next.jsとgsapを使用するプロジェクトがあったので、備忘録を兼ねて記録します。
読者対象
gsapについて導入から基本的な使用方法を知りたい方
gsapを使用してpin-animationを作成したい方
応用編でgsapのtext-animationも作成しているので、text-animationに興味がある方
作りたいもの
pin-animation
section2にpin-animationを適応しています。
しばらく画面にsection2が止まり、backgroud-colorとtext-colorがスクロールと共に変化している様子が確認いただけます。
開発環境
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@types/node": "18.15.3",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"autoprefixer": "^10.4.14",
"eslint": "8.36.0",
"eslint-config-next": "13.2.4",
"gsap": "^3.11.2",
"next": "13.2.4",
"postcss": "^8.4.21",
"react": "18.2.0",
"react-dom": "18.2.0",
"tailwindcss": "^3.2.7",
"tailwindcss-gradients": "^3.0.0",
"typescript": "4.9.5"
}
}
手順
Next.js、Tailwind CSSのセットアップは省略し、gsapの導入から始めます。
gsapの導入
npm install gsap
普段はnpmを使用しているので、npmコマンドを使用します。
CDNなどでも導入可能です、詳しくはgsapの公式サイトを参照ください。
レイアウト作成
今回はTailwind CSSで指定していますが、上記の動画にあったように3つのセクションの箱を作成し、それぞれheightを100vhで指定し、画面いっぱいにして縦に並べただけです。
<div className="h-screen bg-blue w-full flex justify-center items-center">
<p className="text-[100px]">section 1</p>
</div>
<div className="h-screen w-full bg-dark flex items-center justify-center">
<p className="text-[100px]">section 2</p> //背景色白、テキストカラー黒
</div>
<div className="h-screen bg-green w-full flex justify-center items-center">
<p className="text-[100px]">section 3</p>
</div>
Refの適応
Reactでgsapを使用する場合は、refでdomを操作します。
そのためuseRefで作成したrefを操作したいタグに適応します。
section2を囲んでいるdivにrefをつけました。
import { useRef } from "react";
const pinRef = useRef(null);
<div className="h-screen bg-blue w-full flex justify-center items-center">
<p className="text-[100px]">section 1</p>
</div>
<div ref={pinRef} className="h-screen w-full bg-dark flex items-center justify-center">
// refの追加↑
<p className="text-[100px]">section 2</p>
</div>
<div className="h-screen bg-green w-full flex justify-center items-center">
<p className="text-[100px]">section 3</p>
</div>
scrollTriggerプラグインの追加
今回使用するpin-animationはgsapのscrollTriggerの機能で、scrollTriggerはプラグインなので追加する必要があります。
import ScrollTrigger from "gsap/dist/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
⚠️ページ自体に追加してもいいですが、どのページでも使いたい場合は、HeaderなどLayout系(どのページでも読み込まれる要素)に入れておくと、どこでも使えるので便利です。
gsapで操作
domを操作する準備が整ったので、gsapを使用します。
import gsap from "gsap";
useEffect(()=>{
gsap.to(pinRef.current, {
background: "#fff", // 背景色白
color: "#000", // テキストカラー黒
scrollTrigger: {
trigger: pinRef.current,
pin: true,
scrub: true,
},
});
},[])
上から見ていきます。
gsapの関数はuseEffectで囲います。
gsap.toの第一引数には変更したいターゲットを指定します。
今回はrefを使っているのでpinRef.currentを指定します。
gsap.toは今の状態からgsapで指定した状態に変化させるという意味です。
今回の場合、今の状態(背景色が黒、テキストカラーが白)から
gsapで第二引数に指定している状態(背景色が白,テキストカラーが黒)に変化するということになります。
scrollTriggerの内容は、
triggerはアニメーションを発生させる場所の指定です、第一引数と同じくpinRef.currentを指定します。
pinはpin-animationする場合に、trueを指定します
scrubはスクロールに連動してアニメーションさせる場合に、trueを指定します。
完成しました。
import { useEffect, useRef } from "react";
import gsap from "gsap";
import ScrollTrigger from "gsap/dist/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
const PinAnimation = () => {
const pinRef = useRef(null);
useEffect(() => {
gsap.to(pinRef.current, {
background: "#fff",
color: "#000",
scrollTrigger: {
trigger: pinRef.current,
scrub: true,
pin: true,
markers: true,
},
});
}, []);
return (
<>
<div className="h-screen bg-blue w-full flex justify-center items-center">
<p className="text-[100px]">section 1</p>
</div>
<div ref={pinRef} className="h-screen w-full bg-dark flex items-center justify-center">
<p className="text-[100px]">section 2</p>
</div>
<div className="h-screen bg-green w-full flex justify-center items-center">
<p className="text-[100px]">section 3</p>
</div>
</>
);
};
export default PinAnimation;
ページ全体のコードはこんな感じです。
応用編 (text-animation)
pin-animationに合わせて、text-animationも追加してみます。
Textプラグイン追加
text-animationもプラグインが必要なので追加します。
import TextPlugin from "gsap/dist/TextPlugin";
gsap.registerPlugin(TextPlugin);
refの追加
同じくrefで操作するので、追加します。
const textRef = useRef(null);
<div className="h-screen bg-blue w-full flex justify-center items-center">
<p className="text-[100px]">section 1</p>
</div>
<div ref={pinRef} className="h-screen w-full bg-dark flex items-center justify-center">
<p ref={textRef} className="text-[100px]">section 2</p>
// refの追加↑
</div>
<div className="h-screen bg-green w-full flex justify-center items-center">
<p className="text-[100px]">section 3</p>
</div>
gsapで操作
スクロールに応じて、テキストをアニメーションさせます。
その準備として、タグ内にあった"section2"の文字を消し、pタグの箱のみにします。
なぜ消すかはあとで説明があります。
<div className="h-screen bg-blue w-full flex justify-center items-center">
<p className="text-[100px]">section 1</p>
</div>
<div ref={pinRef} className="h-screen w-full bg-dark flex items-center justify-center">
<p ref={textRef} className="text-[100px]" /> // "section 2" 削除
</div>
<div className="h-screen bg-green w-full flex justify-center items-center">
<p className="text-[100px]">section 3</p>
</div>
useEffect(()=>{
gsap.to(textRef.current, {
autoAlpha: 1,
text: "section 2",
scrollTrigger: {
trigger: pinRef.current,
scrub: true,
},
});
},[])
同じようにgsap.toで第一引数にターゲット(textRef.current)を指定。
autoAlphaはアニメーションスタート時点opacity-0からアニメーションの終わりにopacity-100に徐々に変化するための指定です。
textには表示したいテキストを入れます、今回は先ほど削除した"section 2"を入れました。
scrollTriggerのtriggerをpinRef.currentにしているのはアニメーションさせたい範囲の指定なので、
pinRefで指定しているdomの範囲内でアニメーションさせたいからです、その他はスクロール連動を指定しています。
動かしてみるこんな感じです。
テキストがスクロールに応じてアニメーションしています。
widthが指定されていないので、文字が出てくるたびにwidthが変わっているため真ん中からテキストが出てきています。
動きが少し気持ち悪いので、修正します。
微修正
アニメーションが終わる時点のsection2が入っているpタグのwidthは380pxなので、それを前もって要素に持たせてしまいます。
<div className="h-screen bg-blue w-full flex justify-center items-center">
<p className="text-[100px]">section 1</p>
</div>
<div ref={pinRef} className="h-screen w-full bg-dark flex items-center justify-center">
<p ref={textRef} className="text-[100px] w-[380px]" />
// widthを事前に設定↑
</div>
<div className="h-screen bg-green w-full flex justify-center items-center">
<p className="text-[100px]">section 3</p>
</div>
pタグには元からwidthがあるので、左から順番にテキストが出てきているのが確認できます。
import { useEffect, useRef } from "react";
import gsap from "gsap";
import ScrollTrigger from "gsap/dist/ScrollTrigger";
import TextPlugin from "gsap/dist/TextPlugin";
gsap.registerPlugin(ScrollTrigger);
gsap.registerPlugin(TextPlugin);
const PinAnimation = () => {
const textRef = useRef(null);
const pinRef = useRef(null);
useEffect(() => {
gsap.to(pinRef.current, {
background: "#fff",
color: "#000",
scrollTrigger: {
trigger: pinRef.current,
scrub: true,
pin: true,
markers: true,
},
});
gsap.to(textRef.current, {
autoAlpha: 1,
duration: 10,
text: "section 2",
scrollTrigger: {
trigger: pinRef.current,
scrub: true,
pin: true,
markers: true,
},
});
}, []);
return (
<>
<div className="h-screen bg-blue w-full flex justify-center items-center">
<p className="text-[100px]">section 1</p>
</div>
<div ref={pinRef} className="h-screen w-full bg-dark flex items-center justify-center">
<p ref={textRef} className="text-[100px] w-[380px]" />
</div>
<div className="h-screen bg-green w-full flex justify-center items-center">
<p className="text-[100px]">section 3</p>
</div>
</>
);
};
export default PinAnimation;
全体はこんな感じです。
gsap.toを関数に切り出すと、もっとスッキリしますが、また別の機会にします。
最後に
読んでいただきありがとうございました、今回はgsapのpin-animationについて解説しました。
今後も機会があれば、情報発信していきます。
参考資料
公式ページ
scrollTrigger
textPlugin