13
8

More than 1 year has passed since last update.

Next13のApp dirでGSAP使いたいときに試行錯誤したやつ

Last updated at Posted at 2023-01-11

はじめに

この記事は私(超絶初心者)が最近リリースされたNext.js 13 で GSAP(GreenSock Animation Platform)を動かすために、AppディレクトリやHooksやらで試行錯誤したので備忘録として纏め、ついでに誰かのお助けになれればと思い記述しています。

そもそもNext13で何が変わったのか

・app/ Directory (beta): より簡単に、より速く、クライアント側JSのサイズをより小さく。
  ・Layouts
   ・React Server Components
   ・Streaming
・Turbopack (alpha): Rustで実装したWebpackの代替バンドラーで最大700倍高速化。
・New next/image (stable): ネイティブブラウザの遅延ロードを利用し、より高速に。
・New @next/font (beta): レイアウトシフトをゼロにする自動セルフホストフォント。
・Improved next/link: APIを簡素化し、aタグを自動的に表示するようにしました。
(Next.js 13 和訳 から引用)

今回は目玉(だよね?)のAppディレクトリを用いて開発を試みることにしました。

Appによる今までとの変更点

まず、ルートディレクトリがpagesからappへ変わり、今までのindex ファイルがindex.tsxからpage.tsxに変更されました。

app/page.tsx
export default function Page() {
  return(
  <div>
    <p>indexがindex.tsxからpage.tsxに変わりました</p>
  <div>
 )
}

ここでの問題点:
既存のpagesと新規の appで役割が重複しておりrun devを行うとエラーが発生します。

解決方法:
pagesは使用しないので配下のファイルを削除します。
(ポチポチ消す or rm -rf pages)

Layout

これを利用することで複数のページ間でレイアウトおよび内部状態を共有することが可能になる。
詳しくは公式ドキュメントか和訳をご覧ください。(上手く説明出来ないので)

ページのHeader情報をhead.tsxとして管理できたり、stateの状態管理がページ間で共有できたり色々あるらしいです。

よくわからないがとりあえず書いてみる

よくわからない(初心者)なのでとりあえず書きます。

npx create-next-app nextjs13-sample(プロジェクト名) --ts(Typescriptを使うなら入れる) --experimental-app --use-npm

でAppディレクトリ機能が使用可能な状態で開発環境の生成。

componentsフォルダをとりあえず作成し、rfce雛形でファイルを作成。

app/components/Gsap.tsx
import React from 'react'

function Gsap() {
  return <div>じーさっぷ</div>
}

export default Gsap

GSAPのインストール

GSAPを利用したいので、yarn addします。npmでもいいですが猫アイコンが可愛いので今回はyarnで行きます。

yarn add gsap	

GSAPのインポート

上らへんに import文を書いておきます。

app/components/Gsap.tsx
import React from 'react'
import { gsap } from 'gsap'


function Gsap() {
  return  <div>じーさっぷ</div>
}

export default Gsap

これで GSAP自体は利用可能になりました!

GSAPしたい要素を取得

今回は<div>じーさっぷ</div>をGSAPしたいのでuseRef()で取得し、const textRefに格納しておきます。
useRef()等Hooksはfunction内に記述します。

app/components/Gsap.tsx
import React, { useRef } from "react";
import { gsap } from "gsap";

function Gsap() {
  const textRef = useRef<HTMLDivElement | null>(null);

  return <div ref={textRef}>じーさっぷ</div>;
}

export default Gsap;

これで取得することができ・・・ません!!!!!

You're importing a component that needs useLayoutEffect. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.

とエラーが発生しました!

エラーの理由はReact Server Components

Next 13からReact Server Componentsが利用できるようになりました。
これは、
表示系(一覧等)= Server Components
操作系(作成や削除等)= Client Components
と分けて使うことが推奨されています。(引用:next13 和訳より)

今回のuseRefは操作型のためClient Componentsにあたります。
ですが、デフォルトのレンダリング方法がサーバーサイドのため、useRefでエラーが発生しました。

クライアントサイドのレンダリングの場合、文頭に"use client"と記述することでエラーが解消されます。

app/components/Gsap.tsx
"use client";

import React, { useRef } from "react";
import { gsap } from "gsap";

function Gsap() {
  const textRef = useRef<HTMLDivElement | null>(null);

  return <div ref={textRef}>じーさっぷ</div>;
}

export default Gsap;

これでエラーが解消されました!

動いたことだしGSAPしていく

とりあえず動いたことだしGSAPしていきます。今回はマウント後にアニメーションさせたいと思います。
記述方法ですが、Nextだからといって vanillaと変わることはありません。

注意点としては取得した要素はuseRefcurrentに格納されているので、
今回の場合、指定パスはtextRef.currentとするか別の変数にcurrentごと格納して利用してください。

app/components/Gsap.tsx
"use client";

import React, { useRef } from "react";
import { gsap } from "gsap";

function Gsap() {
    const textRef = useRef<HTMLDivElement | null>(null);
     gsap.to(textRef.current, {
      x: "20%",
      color: "red",
    });

  return <div ref={textRef}>じーさっぷ</div>;
}

export default Gsap;

これで動き・・・
img
ません!!!!なんで!!!!!!

公式がドキュメント出してるので見てみる

公式がGetting Started with GSAP + React.としてドキュメントを出してるので見てみます。

しばらく読みすすめるとTriggering animation on mount - useLayoutEffect()と書いてあるのでここを読んでみます。

useLayoutEffect() フックは、React がすべての DOM ミューテーションを実行した直後 に 実行されます。要素がレンダリングされ、アニメーション化の準備が整っていることが保証されるため、これはアニメーションにとって非常に便利なフックです。一般的な構造は次のとおりです。(google翻訳)

const comp = useRef(); // create a ref for the root level element (we'll use it later)

useLayoutEffect(() => {
  
  // -- ANIMATION CODE HERE --
  
  return () => { 
    // cleanup code (optional)
  }
  
}, []); // <- empty dependency Array so it doesn't re-run on every render!

つまるところ、useLayoutEffect()を利用することによりマウント後にアニメーションが開始されるようなので、したがって記述し直します。
記述する際はuseEffectと同じく、空の依存配列( [] ) を第2引数に記述します。

app/components/Gsap.tsx
"use client";

import React, { useRef, useLayoutEffect } from "react";
import { gsap } from "gsap";

function Gsap() {
  const textRef = useRef<HTMLDivElement | null>(null);

  useLayoutEffect(() => {
    gsap.to(textRef.current, {
      x: "20%",
      color: "red",
    });
  }, []);

  return <div ref={textRef}>じーさっぷ</div>;
}

export default Gsap;

これで・・・
image2
動きました!!

GSAP、2回行動する。

実は、見えてないだけで GSAPは二回行動しています。
useLayoutEffect()の中にconsole.log("マウントされました!")と書いて確認してみます。
img3
”2”とカウントがマークされています。
原因はReactの仕様。React18からデフォルト開発環境でStrict modeになっており、これにより開発環境だとマウントが二回発生します。
アニメーションは一回でいいので、didEffectとして一回だけのマウントにしてあげます。
以下のように追記します。

app/components/Gsap.tsx
"use client";

import React, { useRef, useLayoutEffect } from "react";
import { gsap } from "gsap";

function Gsap() {
  const textRef = useRef<HTMLDivElement | null>(null);
  const didEffect = useRef(false);

  useLayoutEffect(() => {
    if (didEffect.current) return;
    didEffect.current = true;
    console.log("マウントされました");
    gsap.to(textRef.current, {
      x: "20%",
      color: "red",
    });
  }, []);

  return <div ref={textRef}>じーさっぷ</div>;
}

export default Gsap;

こうすることにより一回のみマウントされるようになります

参考:

参考記事一覧

13
8
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
13
8