7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

gsap + Next.jsでpin-animationを作成する

Last updated at Posted at 2023-04-17

はじめに

フロントエンドエンジニア歴3年目、現在は80&Companyでフロントエンドを担当しています。
Next.jsgsapを使用するプロジェクトがあったので、備忘録を兼ねて記録します。

読者対象

gsapについて導入から基本的な使用方法を知りたい方
gsapを使用してpin-animationを作成したい方
応用編でgsapのtext-animationも作成しているので、text-animationに興味がある方

作りたいもの

pin-animation

Next-with-Gsap-Animation.gif

section2にpin-animationを適応しています。
しばらく画面にsection2が止まり、backgroud-colortext-colorがスクロールと共に変化している様子が確認いただけます。

開発環境

package.json
{
  "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を指定します。

Next-with-Gsap-Animation.gif

完成しました。

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の範囲内でアニメーションさせたいからです、その他はスクロール連動を指定しています。

Next-with-Gsap-Animation.gif

動かしてみるこんな感じです。
テキストがスクロールに応じてアニメーションしています。
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>

Next-with-Gsap-Animation-1.gif

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

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?