T-CREATOR

Motion(旧 Framer Motion)デザインレビュー運用:Figma パラメータ同期と差分共有のワークフロー

Motion(旧 Framer Motion)デザインレビュー運用:Figma パラメータ同期と差分共有のワークフロー

デザインと実装の間には、常に「見た目は同じなのに動きが違う」という課題が存在します。特にアニメーションを含むコンポーネントでは、Figma で定義したタイミング関数やデュレーションが実装時に正確に反映されず、デザインレビューで何度も指摘と修正を繰り返すことになりがちです。

本記事では、Motion(旧 Framer Motion)を使ったアニメーション実装において、Figma のパラメータを確実に同期し、変更差分を開発チームと共有するワークフローをご紹介します。このワークフローを導入すれば、デザイナーとエンジニアの間での認識のズレが減り、レビューサイクルが大幅に短縮されるでしょう。

背景

デザインツールとアニメーションライブラリの分断

現代の Web 開発では、Figma がデザインの標準ツールとして広く使われています。一方、React ベースのプロジェクトではアニメーション実装に Motion(旧 Framer Motion)が選ばれることが多く、両者は別々のエコシステムで動作しているのが現状です。

Figma ではイージング関数(ease-in、ease-out など)やデュレーション(0.3s、0.5s など)を視覚的に設定できます。しかし、これらのパラメータをエンジニアに伝える際、ドキュメントやコメントで「ease-in-out、300ms」と文字で記載するだけでは、実装時に微妙なズレが生じやすくなります。

デザインレビューでの繰り返し修正

デザインレビューの場で「この動きは少し早すぎる」「イージングが違う気がする」といった指摘が続くと、開発速度が低下するだけでなく、チームのモチベーションにも影響を与えます。特にアニメーションは体感的な要素が強いため、数値だけでは伝わりにくいのが難点です。

以下の図は、従来のワークフローにおける課題を示しています。

mermaidflowchart LR
  designer["デザイナー"] -->|Figma で設計| figma["Figma ファイル"]
  figma -->|口頭・ドキュメント| eng["エンジニア"]
  eng -->|Motion で実装| code["実装コード"]
  code -->|レビュー依頼| designer
  designer -->|差分指摘| eng
  eng -->|再実装| code

  style figma fill:#f9f,stroke:#333
  style code fill:#9cf,stroke:#333

このように、Figma とコード間でパラメータが分断されており、往復のやり取りが発生しやすい構造になっています。

課題

1. パラメータの手動転記によるミス

Figma で定義したアニメーションパラメータを、エンジニアが手動で Motion のコードに転記する際、以下のようなミスが発生します。

  • デュレーションの単位違い: Figma は ms、Motion は秒数で指定するため、300ms0.3 に変換し忘れる
  • イージング関数の名称違い: Figma の ease-in-out が Motion では easeInOut と表記が異なる
  • 遅延(delay)の見落とし: Figma で設定した delay を実装時に忘れる

これらのミスは、レビュー時に初めて発覚することが多く、手戻りコストが大きくなります。

2. 変更差分の追跡困難

デザインが更新された際、「どのパラメータがどう変わったのか」を把握するのが困難です。Figma のバージョン履歴を見ても、アニメーションパラメータの変更は視覚的に分かりにくく、エンジニアが変更点を見逃しやすくなります。

3. レビューコミュニケーションの非効率

デザインレビューでは、「この部分のアニメーションを修正してください」という指摘に対し、具体的な数値が伝わらないことがあります。結果として、何度も試行錯誤を繰り返すことになり、レビューサイクルが長期化します。

以下の図は、主な課題を整理したものです。

mermaidflowchart TB
  subgraph issues["デザインレビューの課題"]
    issue1["パラメータ転記ミス"]
    issue2["差分追跡困難"]
    issue3["レビュー非効率"]
  end

  issue1 -->|原因| cause1["手動転記"]
  issue2 -->|原因| cause2["履歴の可視性低"]
  issue3 -->|原因| cause3["数値伝達不足"]

  cause1 --> impact["手戻りコスト増"]
  cause2 --> impact
  cause3 --> impact

  style issues fill:#fdd,stroke:#333
  style impact fill:#faa,stroke:#333

解決策

Figma と Motion のパラメータ同期ワークフロー

これらの課題を解決するために、以下の 3 つの仕組みを組み合わせたワークフローを構築します。

  1. Figma プラグインによるパラメータ出力: Figma でアニメーションパラメータを JSON 形式で出力
  2. Motion 設定ファイルとの同期: 出力した JSON を Motion の設定として読み込み
  3. 差分管理と共有: Git でパラメータファイルを管理し、変更差分を可視化

このワークフローにより、Figma のパラメータが直接コードに反映され、手動転記ミスがなくなります。また、Git の差分表示により、デザイン変更が明確に追跡できるようになります。

以下の図は、改善後のワークフロー全体像を示しています。

mermaidflowchart LR
  designer["デザイナー"] -->|Figma で設計| figma["Figma ファイル"]
  figma -->|プラグインで出力| json["JSON パラメータ"]
  json -->|Git で管理| repo["リポジトリ"]
  repo -->|読み込み| code["Motion コード"]
  code -->|レビュー依頼| designer
  designer -->|差分確認| repo
  repo -->|更新| json

  style json fill:#9f9,stroke:#333
  style repo fill:#99f,stroke:#333

このフローでは、JSON ファイルが単一の信頼できる情報源(Single Source of Truth)となり、デザイナーとエンジニアが同じパラメータを参照できます。

ワークフローの構成要素

このワークフローを実現するために、以下の技術要素を組み合わせます。

#要素役割技術
1Figma プラグインアニメーションパラメータ出力Figma Plugin API
2JSON パラメータファイルデザイン定義の保存JSON 形式
3Motion 設定読み込みパラメータの実装適用TypeScript, Motion
4バージョン管理差分追跡と共有Git, GitHub

具体例

ステップ 1: Figma プラグインの作成

まず、Figma でアニメーションパラメータを抽出するプラグインを作成します。このプラグインは、選択したレイヤーのトランジション設定を JSON 形式で出力します。

プラグインの基本構造

以下は、Figma プラグインのエントリーポイントです。

typescript// manifest.json の設定
{
  "name": "Motion Parameters Exporter",
  "id": "motion-params-exporter",
  "api": "1.0.0",
  "main": "code.js",
  "ui": "ui.html"
}

プラグインのメイン処理では、選択されたノードからトランジション情報を取得します。

typescript// code.ts(プラグインのメインロジック)

// Figma の選択ノードを取得
const selection = figma.currentPage.selection;

// 選択がない場合のエラーハンドリング
if (selection.length === 0) {
  figma.notify(
    'アニメーション対象のレイヤーを選択してください'
  );
  figma.closePlugin();
}

アニメーションパラメータの抽出

Figma のノードからトランジション情報を抽出する関数を実装します。

typescript// トランジション情報を抽出する関数
interface AnimationParams {
  name: string;
  duration: number; // 秒単位
  easing: string;
  delay: number;
}

function extractTransition(
  node: SceneNode
): AnimationParams | null {
  // FrameNode または ComponentNode のみ対応
  if (node.type !== 'FRAME' && node.type !== 'COMPONENT') {
    return null;
  }

  // reactions(プロトタイプ設定)を確認
  const reactions = node.reactions;
  if (!reactions || reactions.length === 0) {
    return null;
  }

  return reactions
    .filter((r) => r.action?.type === 'NODE')
    .map((r) => ({
      name: node.name,
      duration: r.action.transition.duration / 1000, // ms を秒に変換
      easing: convertEasing(r.action.transition.easing),
      delay: (r.action.transition.delay || 0) / 1000,
    }))[0];
}

イージング関数の変換

Figma のイージング設定を Motion の形式に変換します。

typescript// Figma のイージングを Motion 形式に変換
function convertEasing(easing: any): string {
  const easingMap: Record<string, string> = {
    EASE_IN: 'easeIn',
    EASE_OUT: 'easeOut',
    EASE_IN_OUT: 'easeInOut',
    LINEAR: 'linear',
    EASE_IN_BACK: 'backIn',
    EASE_OUT_BACK: 'backOut',
  };

  return easingMap[easing.type] || 'easeInOut';
}

JSON の出力

抽出したパラメータを JSON 形式で出力します。

typescript// パラメータを JSON として出力
const params = selection
  .map((node) => extractTransition(node))
  .filter((p) => p !== null);

// UI に結果を送信
figma.ui.postMessage({
  type: 'export',
  data: JSON.stringify(params, null, 2),
});

このプラグインを実行すると、以下のような JSON が出力されます。

json[
  {
    "name": "Button Hover",
    "duration": 0.3,
    "easing": "easeInOut",
    "delay": 0
  },
  {
    "name": "Modal Open",
    "duration": 0.5,
    "easing": "backOut",
    "delay": 0.1
  }
]

ステップ 2: パラメータファイルの管理

出力した JSON をプロジェクトリポジトリに配置します。

ディレクトリ構成

プロジェクト内に専用のディレクトリを作成し、デザインパラメータを管理します。

bash/src
  /config
    /animation
      motion-params.json  # Figma から出力したパラメータ
      index.ts            # パラメータ読み込み用モジュール

パラメータファイルの例

json{
  "button": {
    "hover": {
      "duration": 0.3,
      "easing": "easeInOut",
      "delay": 0
    },
    "press": {
      "duration": 0.15,
      "easing": "easeIn",
      "delay": 0
    }
  },
  "modal": {
    "open": {
      "duration": 0.5,
      "easing": "backOut",
      "delay": 0.1
    },
    "close": {
      "duration": 0.3,
      "easing": "easeIn",
      "delay": 0
    }
  }
}

ステップ 3: Motion での読み込み実装

パラメータファイルを Motion のコンポーネントで利用します。

型定義の作成

まず、アニメーションパラメータの型を定義します。

typescript// src/config/animation/types.ts

// アニメーションパラメータの型定義
export interface MotionParams {
  duration: number;
  easing: string;
  delay: number;
}

// パラメータセット全体の型
export interface AnimationConfig {
  button: {
    hover: MotionParams;
    press: MotionParams;
  };
  modal: {
    open: MotionParams;
    close: MotionParams;
  };
}

パラメータ読み込みモジュール

JSON ファイルを読み込み、型安全にアクセスできるモジュールを作成します。

typescript// src/config/animation/index.ts

import motionParamsJson from './motion-params.json';
import type { AnimationConfig } from './types';

// JSON を型付きオブジェクトとして export
export const animationConfig: AnimationConfig =
  motionParamsJson;

// 特定のアニメーションパラメータを取得するヘルパー関数
export function getAnimationParams(
  category: keyof AnimationConfig,
  action: string
): MotionParams {
  const params = animationConfig[category][action];

  if (!params) {
    console.warn(
      `Animation params not found: ${category}.${action}`
    );
    // デフォルト値を返す
    return { duration: 0.3, easing: 'easeInOut', delay: 0 };
  }

  return params;
}

Motion コンポーネントでの利用

パラメータを実際の Motion コンポーネントに適用します。

typescript// src/components/Button.tsx

import { motion } from 'motion/react';
import { getAnimationParams } from '@/config/animation';

export function Button({
  children,
}: {
  children: React.ReactNode;
}) {
  // Figma のパラメータを取得
  const hoverParams = getAnimationParams('button', 'hover');
  const pressParams = getAnimationParams('button', 'press');

  return (
    <motion.button
      whileHover={{
        scale: 1.05,
        transition: {
          duration: hoverParams.duration,
          ease: hoverParams.easing,
          delay: hoverParams.delay,
        },
      }}
      whileTap={{
        scale: 0.95,
        transition: {
          duration: pressParams.duration,
          ease: pressParams.easing,
          delay: pressParams.delay,
        },
      }}
    >
      {children}
    </motion.button>
  );
}

モーダルコンポーネントの例

より複雑なアニメーションを持つモーダルコンポーネントでも同様に適用できます。

typescript// src/components/Modal.tsx

import { motion, AnimatePresence } from 'motion/react';
import { getAnimationParams } from '@/config/animation';

interface ModalProps {
  isOpen: boolean;
  onClose: () => void;
  children: React.ReactNode;
}

export function Modal({
  isOpen,
  onClose,
  children,
}: ModalProps) {
  // Figma 定義のパラメータを使用
  const openParams = getAnimationParams('modal', 'open');
  const closeParams = getAnimationParams('modal', 'close');

  return (
    <AnimatePresence>
      {isOpen && (
        <motion.div
          initial={{ opacity: 0, scale: 0.9 }}
          animate={{
            opacity: 1,
            scale: 1,
            transition: {
              duration: openParams.duration,
              ease: openParams.easing,
              delay: openParams.delay,
            },
          }}
          exit={{
            opacity: 0,
            scale: 0.9,
            transition: {
              duration: closeParams.duration,
              ease: closeParams.easing,
              delay: closeParams.delay,
            },
          }}
          onClick={onClose}
          className='modal-overlay'
        >
          {children}
        </motion.div>
      )}
    </AnimatePresence>
  );
}

ステップ 4: Git による差分管理

パラメータファイルを Git で管理することで、デザイン変更の履歴を追跡できます。

コミット例

デザイナーがパラメータを更新した際のコミット例です。

bash# パラメータファイルの変更をステージング
git add src/config/animation/motion-params.json

# わかりやすいコミットメッセージ
git commit -m "design: モーダルの開閉アニメーションを高速化(0.5s → 0.4s)"

プルリクエストでの差分確認

GitHub のプルリクエスト画面では、JSON の差分が視覚的に確認できます。

diff{
  "modal": {
    "open": {
-     "duration": 0.5,
+     "duration": 0.4,
      "easing": "backOut",
      "delay": 0.1
    }
  }
}

このように、具体的な数値変更が一目で分かるため、レビュアーは変更内容を正確に把握できます。

レビューコメントの例

プルリクエストでのレビューコメントも、具体的な数値を参照しながら行えます。

markdown# レビューコメント例

モーダルの開くアニメーションが 0.4s に短縮されていますが、
少し速すぎる印象です。0.45s くらいが適切かと思います。

変更後の動作確認済み ✅

ステップ 5: 自動化とワークフロー統合

GitHub Actions での検証

パラメータファイルの形式が正しいか、自動でチェックする仕組みを導入します。

yaml# .github/workflows/validate-animation-params.yml

name: Validate Animation Parameters

on:
  pull_request:
    paths:
      - 'src/config/animation/motion-params.json'

jobs:
  validate:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: yarn install

      - name: Validate JSON schema
        run: yarn validate:animation-params

バリデーションスクリプト

パラメータファイルの妥当性を検証するスクリプトです。

typescript// scripts/validate-animation-params.ts

import { readFileSync } from 'fs';
import { join } from 'path';

// スキーマ定義(簡易版)
interface ParamSchema {
  duration: number;
  easing: string;
  delay: number;
}

// 許可されたイージング関数
const ALLOWED_EASINGS = [
  'linear',
  'easeIn',
  'easeOut',
  'easeInOut',
  'backIn',
  'backOut',
  'backInOut',
];

function validateParams(params: any): boolean {
  let isValid = true;

  // 再帰的にパラメータを検証
  function validate(obj: any, path: string = ''): void {
    if (obj.duration !== undefined) {
      // duration の検証
      if (
        typeof obj.duration !== 'number' ||
        obj.duration <= 0
      ) {
        console.error(
          `❌ ${path}.duration: 正の数値である必要があります`
        );
        isValid = false;
      }

      // easing の検証
      if (!ALLOWED_EASINGS.includes(obj.easing)) {
        console.error(
          `❌ ${path}.easing: 許可されていない値です`
        );
        isValid = false;
      }

      // delay の検証
      if (typeof obj.delay !== 'number' || obj.delay < 0) {
        console.error(
          `❌ ${path}.delay: 0以上の数値である必要があります`
        );
        isValid = false;
      }
    } else {
      // ネストされたオブジェクトを検証
      for (const key in obj) {
        validate(obj[key], path ? `${path}.${key}` : key);
      }
    }
  }

  validate(params);
  return isValid;
}

// メイン処理
const paramsPath = join(
  __dirname,
  '../src/config/animation/motion-params.json'
);
const params = JSON.parse(
  readFileSync(paramsPath, 'utf-8')
);

if (validateParams(params)) {
  console.log('✅ アニメーションパラメータは正常です');
  process.exit(0);
} else {
  console.error(
    '❌ アニメーションパラメータに問題があります'
  );
  process.exit(1);
}

package.json へのスクリプト追加

json{
  "scripts": {
    "validate:animation-params": "ts-node scripts/validate-animation-params.ts"
  }
}

ステップ 6: デザインレビューフローの改善

このワークフローを導入した後のデザインレビューの流れを整理します。

mermaidsequenceDiagram
  participant Designer as デザイナー
  participant Figma as Figma
  participant Plugin as プラグイン
  participant Git as Git リポジトリ
  participant Engineer as エンジニア
  participant Code as Motion コード

  Designer->>Figma: アニメーション設計
  Designer->>Plugin: パラメータ出力
  Plugin->>Git: JSON コミット
  Git->>Engineer: PR 作成通知
  Engineer->>Git: 差分確認
  Engineer->>Code: パラメータ読み込み
  Code->>Engineer: 実装完了
  Engineer->>Designer: レビュー依頼
  Designer->>Code: 動作確認
  Designer->>Git: 承認

このフローでは、以下の改善が実現されています。

  • パラメータの自動同期: 手動転記が不要
  • 差分の可視化: Git で変更内容が明確
  • レビューの効率化: 具体的な数値でコミュニケーション

実運用での Tips

実際にこのワークフローを運用する際の実践的なポイントをご紹介します。

1. パラメータの命名規則

JSON ファイル内のキー名は、Figma のレイヤー名と一致させることで、追跡しやすくなります。

json{
  "button_primary_hover": {
    "duration": 0.3,
    "easing": "easeInOut",
    "delay": 0
  }
}

2. デフォルト値の設定

すべてのアニメーションを JSON で管理する必要はありません。汎用的なものはデフォルト値として定義します。

typescript// src/config/animation/defaults.ts

export const DEFAULT_ANIMATION = {
  duration: 0.3,
  easing: 'easeInOut',
  delay: 0,
};

export function withDefaults(
  params: Partial<MotionParams>
): MotionParams {
  return { ...DEFAULT_ANIMATION, ...params };
}

3. Storybook での確認

パラメータ変更後の動作を Storybook で確認できるようにします。

typescript// src/components/Button.stories.tsx

import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
import { animationConfig } from '@/config/animation';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  parameters: {
    // 現在のアニメーションパラメータを表示
    docs: {
      description: {
        component: `
          Hover animation: ${JSON.stringify(
            animationConfig.button.hover,
            null,
            2
          )}
        `,
      },
    },
  },
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Default: Story = {
  args: {
    children: 'Click me',
  },
};

まとめ

本記事では、Motion(旧 Framer Motion)を使ったアニメーション実装において、Figma のパラメータを確実に同期し、デザインレビューを効率化するワークフローをご紹介しました。

このワークフローの要点は以下の通りです。

  • Figma プラグインで JSON 出力: アニメーションパラメータを自動抽出し、手動転記ミスを防止
  • Git による差分管理: デザイン変更を明確に追跡し、レビュアーが正確に把握可能
  • Motion での自動読み込み: パラメータファイルを単一の情報源とし、実装との同期を保証
  • 自動検証の導入: GitHub Actions でパラメータの妥当性をチェック

このアプローチにより、デザイナーとエンジニアの間での認識のズレが減り、レビューサイクルが大幅に短縮されます。また、デザインシステムの一貫性を保ちながら、アニメーションの品質を向上させることができるでしょう。

アニメーションは、ユーザー体験を左右する重要な要素です。このワークフローを導入することで、デザイン意図を正確に実装に反映し、より洗練された UI を実現していただければ幸いです。

関連リンク