SSGOI LogoSSGOI

要素アニメーション

個々のDOM要素にアニメーションを適用

SSGOI
from0
to1
spring

トランジションの例

様々なトランジション効果の使用方法:

import { transition } from '@ssgoi/react';
import { fade, scale, blur, slide, fly, rotate, bounce } from '@ssgoi/react/transitions';

/**
 * [React] Auto Key Plugin
 *
 * プラグイン使用時、keyを省略できます。
 * ファイル:行:列に基づいて一意のkeyが自動生成されます。
 *
 * Next.js 設定:
 * ```ts
 * // next.config.ts
 * import SsgoiAutoKey from "@ssgoi/react/unplugin/webpack";
 *
 * const nextConfig = {
 *   webpack: (config) => {
 *     config.plugins.push(SsgoiAutoKey());
 *     return config;
 *   },
 * };
 * ```
 *
 * ⚠️ 注意: .map()リストではJSX keyだけで十分 - プラグインが自動処理
 *
 * // .map()リストではJSX keyだけで十分
 * // プラグインが自動的にfile:line:col:${jsxKey}を生成
 * {items.map((item) => (
 *   <div
 *     key={item.id}  // JSX keyだけで十分
 *     ref={transition(fade())}
 *   >
 *     {item.name}
 *   </div>
 * ))}
 *
 * プラグインなしの場合、keyは必須です。
 */

// Fadeトランジション
<div ref={transition(fade({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
  Fade効果
</div>

// Scaleトランジション
<div ref={transition(scale({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
  Scale効果
</div>

// Blurトランジション
<div ref={transition(blur({ amount: 10, physics: { spring: { stiffness: 300, damping: 30 } } }))}>
  Blur効果
</div>

// Slideトランジション(方向指定)
<div ref={transition(slide({ direction: 'left', physics: { spring: { stiffness: 300, damping: 30 } } }))}>
  Slide効果
</div>

// Flyトランジション(カスタム位置)
<div ref={transition(fly({ x: 200, y: -50, physics: { spring: { stiffness: 300, damping: 30 } } }))}>
  Fly効果
</div>

// Rotateトランジション
<div ref={transition(rotate({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
  Rotate効果
</div>

// Bounceトランジション
<div ref={transition(bounce({ physics: { spring: { stiffness: 300, damping: 30 } } }))}>
  Bounce効果
</div>

// 明示的なkey(プラグインなしの場合は必須)
<div ref={transition({
  key: "explicit-key",
  ...fade({ physics: { spring: { stiffness: 300, damping: 30 } } })
})}>
  明示的なkeyを使用
</div>

基本構造

TransitionConfigインターフェース

interface TransitionConfig {
  physics?: {
    // Spring: スプリング物理 (ease-out効果、デフォルト)
    spring?: {
      stiffness: number;  // スプリング剛性 (デフォルト: 300)
      damping: number;    // 減衰係数 (デフォルト: 30)
      doubleSpring?: boolean | number; // ダブルスプリング効果 (デフォルト: false)
    };
    // Inertia: 慣性物理 (ease-in効果)
    inertia?: {
      acceleration: number; // 加速度 (デフォルト: 500)
      resistance: number;   // 抵抗係数 (デフォルト: 10)
    };
    // Custom Integrator: カスタム物理エンジン
    integrator?: () => Integrator;
  };
  // 2つのアニメーションモードから1つを選択(両方は使用不可)
  tick?: (progress: number) => void; // RAFベースのアニメーション
  css?: (progress: number) => StyleObject; // Web Animation APIベース(推奨)
  prepare?: (element: HTMLElement) => void; // アニメーション開始前の初期設定
  onStart?: () => void;
  onEnd?: () => void;
}

アニメーションモード: css vs tick

SSGOIは2つのアニメーションモードをサポートしています:

物理エンジンオプション

SSGOIは様々な物理エンジンをサポートし、自然なアニメーションを実現します:

Spring (スプリング物理)

ease-out効果を提供するデフォルトの物理エンジンです。アニメーションが速く始まり、目標地点に到達すると自然に減速します。

{
  in: (element) => ({
    physics: {
      spring: {
        stiffness: 300,  // スプリング剛性 (高いほど速い)
        damping: 30,     // 減衰係数 (高いほど弾性が減少)
        doubleSpring: false  // ダブルスプリング効果
      }
    },
    css: (progress) => ({
      opacity: progress,
      transform: `translateY(${(1 - progress) * 20}px)`,
    }),
  })
}

Inertia (慣性物理)

ease-in効果を提供する物理エンジンです。アニメーションがゆっくり始まり、徐々に加速します。

{
  in: (element) => ({
    physics: {
      inertia: {
        acceleration: 500,  // 加速度 (高いほど速く加速)
        resistance: 10      // 抵抗係数 (高いほど減速)
      }
    },
    css: (progress) => ({
      opacity: progress,
      transform: `scale(${0.8 + progress * 0.2})`,
    }),
  })
}

Custom Integrator (カスタム物理エンジン)

独自の物理エンジンを実装して使用できます。

{
  in: (element) => ({
    physics: {
      integrator: () => ({
        next: (dt, state) => {
          // カスタム物理計算ロジック
          return newState;
        }
      })
    },
    css: (progress) => ({
      opacity: progress,
    }),
  })
}

アニメーションモード: css vs tick

SSGOIは2つのアニメーションモードをサポートしています:

cssモード(推奨)

{
  in: (element) => ({
    physics: { spring: { stiffness: 300, damping: 30 } },
    css: (progress) => ({
      opacity: progress,
      transform: `translateY(${(1 - progress) * 20}px)`,
    }),
  })
}
  • Web Animation APIを使用してメインスレッドの負荷を最小化
  • スプリング物理演算を事前計算してキーフレームに変換
  • GPUアクセラレーションによるスムーズなアニメーション
  • パフォーマンスが優れているため、ほとんどの場合推奨

tickモード

{
  in: (element) => ({
    physics: { spring: { stiffness: 300, damping: 30 } },
    tick: (progress) => {
      element.style.opacity = String(progress);
      element.style.transform = `translateY(${(1 - progress) * 20}px)`;
    },
  })
}
  • requestAnimationFrameベースで毎フレームコールバックを実行
  • 直接的なDOM操作が必要な複雑なアニメーションに適している
  • アニメーション中に動的に値を変更する必要がある場合に有用

csstickは同時に使用できません。どちらか1つを選択してください。

トランジションの定義

interface Transition {
  in?: (element: HTMLElement) => TransitionConfig;
  out?: (element: HTMLElement) => TransitionConfig;
}

動作の仕組み

  1. マウント時: 要素がDOMに追加されたときin関数を実行
  2. アンマウント時: 要素が削除される前にout関数を実行
  3. アニメーション: スプリング物理エンジンがprogressを生成
    • in: 0 → 1
    • out: 1 → 0
  4. tickコールバック: 毎フレーム呼び出されてスタイルを更新

トランジションプリセット

import { fade, scale /** etc */ } from "@ssgoi/react/transitions";

フレームワーク別の使用方法

import { transition } from "@ssgoi/react";

<div
  ref={transition({
    key: "unique-key",
    in: (element) => ({
      tick: (progress) => {
        element.style.opacity = progress;
        element.style.transform = `translateY(${20 * (1 - progress)}px)`;
      },
    }),
    out: (element) => ({
      tick: (progress) => {
        element.style.opacity = 1 - progress;
      },
    }),
  })}
>
  コンテンツ
</div>

Progressの動作

key

  • keyはページ内で一意である必要があります(DOMが作成された後削除されたり、削除された後作成されたりしてもアニメーション状態を追跡できるようにするため)
  • React: Auto Key Pluginを使用すると、keyを省略可能(詳細はReactタブのコメントを参照)
  • その他のフレームワーク: keyは必須です

inアニメーション

  • progress: 0 → 1
  • 要素が表示されるときに実行
  • 不透明度が0から1へ、小さいサイズから元のサイズへ

outアニメーション

  • progress: 1 → 0
  • 要素が消えるときに実行
  • 不透明度が1から0へ、元のサイズから小さいサイズへ
// 例: inとoutの違い
{
  in: (element) => ({
    tick: (progress) => {
      // progress: 0 → 1
      element.style.opacity = progress;  // 0 → 1
    }
  }),
  out: (element) => ({
    tick: (progress) => {
      // progress: 1 → 0
      element.style.opacity = progress;  // 1 → 0
    }
  })
}

prepareコールバック

アニメーション開始前にDOM要素を準備する段階:

{
  in: {
    prepare: (element) => {
      // tick実行前に初期状態を設定
      element.style.willChange = 'opacity, transform';
    },
    tick: (progress) => ({
      opacity: progress,
      transform: `translateY(${20 * (1 - progress)}px)`
    })
  }
}

TransitionScope

TransitionScopeは子要素のアニメーション動作を制御するコンテナです。親と子が同時にマウント/アンマウントされるときに不要なアニメーションをスキップできます。

使用シナリオ

  • ページ遷移: ページ遷移時に個々の要素が別々にアニメーションしないようにする
  • モーダル/ダイアログ: コンテナと一緒に表示されるコンテンツの重複アニメーションを防止
  • リスト: リスト全体がマウント/アンマウントされるときに個々のアイテムのアニメーションをスキップ

scopeオプション

interface TransitionOptions {
  // ...
  scope?: 'global' | 'local';
}
  • global(デフォルト): 常にアニメーションを実行
  • local: TransitionScopeと同時にマウント/アンマウントされるときはアニメーションをスキップ

使用例

import { TransitionScope, transition } from '@ssgoi/react';

function MyComponent() {
  const [show, setShow] = useState(true);

  return (
    <>
      <button onClick={() => setShow(!show)}>Toggle</button>

      {show && (
        <TransitionScope>
          <div style={{ padding: '2rem', border: '1px dashed #ccc' }}>
            {/* local: Scopeと同時にマウント/アンマウント時はアニメーションをスキップ */}
            <div
              ref={transition({
                key: 'local-child',
                scope: 'local',
                in: () => ({
                  css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
                }),
                out: () => ({
                  css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
                }),
              })}
            >
              Local Scope(Scopeと一緒に表示/非表示)
            </div>

            {/* global: 常にアニメーションを実行 */}
            <div
              ref={transition({
                key: 'global-child',
                // scope: 'global'はデフォルト
                in: () => ({
                  css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
                }),
                out: () => ({
                  css: (p) => ({ opacity: p, transform: `scale(${0.5 + p * 0.5})` }),
                }),
              })}
            >
              Global Scope(常にアニメーション)
            </div>
          </div>
        </TransitionScope>
      )}
    </>
  );
}

動作

状況scope: 'local'scope: 'global'
Scopeと同時にマウントアニメーションをスキップアニメーションを実行
Scopeと同時にアンマウントアニメーションをスキップアニメーションを実行
Scope内で個別にトグルアニメーションを実行アニメーションを実行