目次
情報確認日:2026年10月16日。GSAP公式資料を参照した、本サイト独自の実装例です。
ReactのコンポーネントでGSAPを扱うとき、初期化だけでなく依存値の変更とアンマウントまで設計する必要があります。useGSAP、scope、revertOnUpdate、contextSafeを二つのカードで確かめます。
スポンサーリンク
Reactの描画とGSAPの変更を分ける
Reactは状態からDOMを描画し、GSAPはその要素へアニメーションを加えます。コンポーネント関数の実行中にTweenを作ると、レンダーのたびに処理を重ねる原因になります。useGSAPを使い、要素が参照できる段階で作成と後片付けを対応させます。
この例は、二枚のカードそれぞれに移動距離の状態と要素参照を持たせます。Reactはカードの存在、文章、ボタンを管理し、GSAPは装飾用の箱のtransformを扱います。同じstyle属性を双方から頻繁に更新する設計を避け、どちらが何を決めるかを明確にします。
インストールとプラグイン登録
React対応の既存プロジェクトへgsapと@gsap/reactを追加し、useGSAPをregisterPluginへ渡します。この二つに加え、プロジェクト側でReactとReact DOMの適切な組み合わせが必要です。サンプルはReact用の開発環境で動かし、WordPressのコードブロック内で直接実行するものではありません。
通常の静的ページでReactを導入する必要がない場合まで、この構成へ置き換える必要はありません。ここではすでにReactで状態や画面切り替えを管理しているケースを対象にしています。React以外のGSAP基本操作は、既存の入門記事の構成を利用できます。
スポンサーリンク
scopeはカード内の対象を限定する
containerのrefをscopeに指定すると、そのコンテキストが扱うセレクター文字列をコンテナー内へ絞れます。回転処理は.boxという共通のクラス名を使いますが、カードAから操作してもカードBへ広がらない構成です。ページ全体をdocument.querySelectorAllで探す場合との違いを確認します。
scopeは要素を自動で作ったり、すべての非同期処理を管理したりする設定ではありません。箱そのものはboxのrefで参照し、利用可能な幅はtrackのrefから読みます。コンテナーがまだ存在しない状態や、条件分岐で対象が外れた状態を別の構成へ持ち込む場合は、参照の有無も扱ってください。
dependenciesとrevertOnUpdateを一組で考える
この例ではdistanceが変わると移動を作り直します。useGSAPのdependenciesへ値を入れるだけでなく、revertOnUpdate:trueも設定し、前回作った状態を更新時に戻します。公式資料でも、依存値更新時の復元を望む場合はこの指定を示しています。
毎回x:0から始める仕様なので、ボタンを押すと箱がいったん出発点へ戻ります。現在位置から連続して目標へ移動する仕様とは異なります。滑らかな追従が必要なら、作成済みTweenの更新や別の設計を検討し、revertOnUpdateを付ければ連続性まで解決するとは考えません。
スポンサーリンク
要素の幅を使って移動範囲を制限する
状態の80または180は希望値です。実際にはトラックのclientWidthから箱のoffsetWidthを引き、利用可能な範囲を超えないようにします。画面が狭いときも箱が丸ごと見えなくなるのを防ぐための、簡単な制限です。
この値を計算するのは初期化とdistance更新の時点です。ウィンドウ幅や親レイアウトの変更へ継続追従する機能は含めていません。常時の幅変更に対応する場合はResizeObserverなどを追加し、監視の解除も設計します。毎レンダーで測定を繰り返すより、何を契機に再測定するかを先に決めます。
後から起こる操作はcontextSafeで包む
クリック時に新しいTweenを作る回転処理は、useGSAPが返すcontextSafeで包みます。これにより、イベントのタイミングで作られたGSAPの処理をコンテキストへ関連付け、対象のscopeと後片付けを利用できます。初期化のコールバックだけにTweenがある場合との違いです。
contextSafeは権限管理や任意の副作用の自動取消ではありません。自分でsetTimeoutや購読、DOMのイベントリスナーを登録したなら、それらの解除は別に行います。今回のボタンはReactのonClickを使い、手作業でaddEventListenerを追加していません。
StrictModeで後片付けを確かめる
エントリーではStrictModeを有効にします。Reactの開発時にはEffectの追加のセットアップとクリーンアップが行われ、解除漏れを発見する助けになります。本番でも常に同じ回数だけ実行される、と説明するものではありません。
カードの表示切り替えでアンマウントと再マウントを試し、回転Tweenが外れた要素へ残らないかを見ます。一度しか操作しない確認では、初期化の問題と後片付けの問題を区別しにくいためです。警告を消すためだけにStrictModeを外すより、解除が対応しているか調べます。
サーバー側の描画がある場合の境界
このサンプルはクライアント側のReact環境を前提にしています。サーバーコンポーネントを使うフレームワークでは、フックやブラウザー操作を置くクライアント境界が必要です。ただし、すべてのReactプロジェクトへ無条件にuse clientを書く必要があるわけではありません。
サーバー出力のHTMLが、JavaScriptの実行前から読めることも確認します。初期状態ですべてを透明にしてからアニメーションだけで表示させる設計は、読み込み失敗時の表示を検討してから採用します。今回の箱は装飾で、見出しや文章の可読性を動作開始に依存させません。
スポンサーリンク
テストは対象の独立性と解除を中心にする
カードAの回転でBが動かないこと、距離変更後も操作できること、表示を外して戻してもTweenが積み重ならないことを確認します。動きを減らす設定では時間を0にし、操作の意味自体は残します。初期化とクリックをそれぞれ試すことが大切です。
模擬DOMでは寸法を固定するため、レスポンシブな実表示まで保証できません。実ブラウザーで幅、フォーカス、開発用と本番用ビルドを確認し、必要な状態更新だけがアニメーションの再作成を起こすか点検してください。
実装例と配置するファイル
既存のReact対応Viteなどの開発環境を前提に、npm install gsap @gsap/react を実行します。以下のコンポーネントをApp.jsxへ、CSSを通常のスタイルシートへ置きます。既存のReactとReact DOMはプロジェクトの組み合わせを保ち、別の版を重複導入しません。
.motion-card { max-width:32rem; padding:1rem; margin:1rem; border:1px solid #bbb; }
.track { padding:1rem 0; overflow:hidden; }
.box { width:3rem; height:3rem; background:#7950c8; border-radius:.5rem; }
.motion-card button { padding:.7rem; margin:.3rem; }
button:focus-visible { outline:3px solid #253b91; }
import { useRef, useState } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";
gsap.registerPlugin(useGSAP);
function MotionCard({ label }) {
const container = useRef(null);
const box = useRef(null);
const track = useRef(null);
const [distance, setDistance] = useState(80);
const reduce = () => matchMedia("(prefers-reduced-motion: reduce)").matches;
const { contextSafe } = useGSAP(() => {
const available = Math.max(0, track.current.clientWidth - box.current.offsetWidth);
gsap.fromTo(box.current, { x: 0 }, {
x: Math.min(distance, available), duration: reduce() ? 0 : 0.8,
ease: "power2.out"
});
}, { scope: container, dependencies: [distance], revertOnUpdate: true });
const rotate = contextSafe(() => {
gsap.to(".box", {
rotation: "+=90", duration: reduce() ? 0 : 0.4,
ease: "power2.out", overwrite: "auto"
});
});
return <section className="motion-card" ref={container}>
<h2>{label}</h2>
<div className="track" ref={track}><div className="box" ref={box} aria-hidden="true" /></div>
<p>移動距離の希望値:{distance}px</p>
<button type="button" onClick={() => setDistance(d => d === 80 ? 180 : 80)}>移動距離を変更</button>
<button type="button" onClick={rotate}>このカードだけ回転</button>
</section>;
}
export default function App() {
const [visible, setVisible] = useState(true);
return <main>
<button type="button" onClick={() => setVisible(v => !v)}>カードの表示を切り替え</button>
{visible && <><MotionCard label="カードA" /><MotionCard label="カードB" /></>}
</main>;
}
エントリーファイルでAppを読み込みます。HTMLにはid="root"の要素を用意してください。
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";
import "./style.css";
createRoot(document.getElementById("root")).render(<StrictMode><App /></StrictMode>);
スポンサーリンク
公開前の確認
掲載コードはGSAP 3.15.0を用いたDOM模擬環境で初期化と主要操作を確認しています。実ブラウザーの描画、フォントの改行、タッチの挙動、支援技術による読み上げは別途確認が必要です。開発用の環境だけでなく、対象端末と本番用ビルドで、初期表示・連打・停止・画面の再表示を確認してください。



![JavaScriptモダンプログラミング完全ガイド [堅牢なコードを効率的に開発できる! ]](https://m.media-amazon.com/images/I/91NvEBSfCkL._SL1500_.jpg)