はじめに
この記事は私(超絶初心者)が最近リリースされた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
に変更されました。
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
雛形でファイルを作成。
import React from 'react'
function Gsap() {
return <div>じーさっぷ</div>
}
export default Gsap
GSAPのインストール
GSAPを利用したいので、yarn addします。npmでもいいですが猫アイコンが可愛いので今回はyarnで行きます。
yarn add gsap
GSAPのインポート
上らへんに import文を書いておきます。
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内に記述します。
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"
と記述することでエラーが解消されます。
"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と変わることはありません。
注意点としては取得した要素はuseRef
のcurrent
に格納されているので、
今回の場合、指定パスはtextRef.current
とするか別の変数にcurrentごと格納して利用してください。
"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;
公式がドキュメント出してるので見てみる
公式が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引数に記述します。
"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;
GSAP、2回行動する。
実は、見えてないだけで GSAPは二回行動しています。
useLayoutEffect()
の中にconsole.log("マウントされました!")と書いて確認してみます。
”2”とカウントがマークされています。
原因はReactの仕様。React18からデフォルト開発環境でStrict mode
になっており、これにより開発環境だとマウントが二回発生します。
アニメーションは一回でいいので、didEffect
として一回だけのマウントにしてあげます。
以下のように追記します。
"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;
こうすることにより一回のみマウントされるようになります
参考:
参考記事一覧