T-CREATOR

Motion(旧 Framer Motion)Variants 完全攻略:staggerChildren・when で複雑アニメを整理する

Motion(旧 Framer Motion)Variants 完全攻略:staggerChildren・when で複雑アニメを整理する

Modern Web アプリケーションにおいて、ユーザーエクスペリエンスを向上させるアニメーションは必要不可欠な要素となっています。しかし、複数の要素を組み合わせた複雑なアニメーションの実装は、多くの開発者が頭を悩ませる課題でもあります。

Motion(旧 Framer Motion)の Variants システムは、この課題を解決する強力な機能です。特に staggerChildrenwhen という 2 つの機能を組み合わせることで、従来では困難だった複雑なアニメーション制御を、整理された形で実装できるようになります。

本記事では、これらの機能の基礎から応用まで、実際のコード例と共に詳しく解説していきます。アニメーション実装に悩む開発者の皆さまにとって、実践的な知識となることでしょう。

背景

Motion Variants とは

従来の Web アニメーション実装では、個々の要素に対して個別にアニメーション設定を行う必要がありました。この手法では、要素数が増えるにつれて管理が困難になり、保守性の低いコードが生まれがちでした。

以下は従来の個別アニメーション管理の例です。

javascript// 従来の個別管理(問題のあるパターン)
const item1 = useRef(null);
const item2 = useRef(null);
const item3 = useRef(null);

// 各要素を個別に制御
const animateItems = () => {
  gsap.to(item1.current, { opacity: 1, duration: 0.3 });
  gsap.to(item2.current, {
    opacity: 1,
    duration: 0.3,
    delay: 0.1,
  });
  gsap.to(item3.current, {
    opacity: 1,
    duration: 0.3,
    delay: 0.2,
  });
};

Motion Variants は、アニメーションの状態を 統一的に管理 するシステムです。複数の要素が共通の状態を持ち、親要素の状態変更によって子要素のアニメーションが自動的に実行される仕組みを提供します。

Variants システムの基本概念を図で示します。

mermaidflowchart TD
  parent[親コンポーネント<br/>variants定義] -->|状態変更| childA[子要素A<br/>自動アニメーション]
  parent -->|状態変更| childB[子要素B<br/>自動アニメーション]
  parent -->|状態変更| childC[子要素C<br/>自動アニメーション]

  style parent fill:#e1f5fe
  style childA fill:#f3e5f5
  style childB fill:#f3e5f5
  style childC fill:#f3e5f5

この図が示すように、親要素で定義された Variants 設定が、子要素に自動的に適用されます。これにより、個別管理の煩雑さから解放されるのです。

staggerChildren と when の位置づけ

Motion エコシステムにおいて、staggerChildrenwhen は高度なアニメーション制御を可能にする重要な機能です。

staggerChildren時差制御 を担当し、複数の子要素を段階的にアニメーションさせます。一方、when条件分岐制御 を担当し、特定の条件に基づいてアニメーションの実行タイミングを制御します。

Motion アニメーション制御の階層構造を以下の図で表現します。

mermaidflowchart LR
  variants[Motion Variants] --> stagger[staggerChildren<br/>時差制御]
  variants --> when_control[when<br/>条件分岐制御]
  variants --> basic[基本アニメーション<br/>個別制御]

  stagger --> timing[タイミング調整]
  when_control --> condition[条件判定]

  style variants fill:#2196f3,color:#fff
  style stagger fill:#4caf50,color:#fff
  style when_control fill:#ff9800,color:#fff

これらの機能により、従来では複雑な JavaScript ロジックが必要だったアニメーション制御を、宣言的なアプローチで実現できるようになります。

課題

複雑アニメーションの管理問題

現代の Web アプリケーションでは、ユーザーの注意を適切に誘導し、情報の階層性を視覚的に表現するため、複数の要素が連携したアニメーションが求められます。しかし、このような複雑なアニメーションの実装には以下の課題が存在します。

複数要素の同期問題

複数の要素を同時にアニメーションさせる際、各要素の開始タイミングや終了タイミングを正確に制御することは困難です。特に、要素数が動的に変化する場合や、非同期でデータが読み込まれる場合には、同期の複雑さが増大します。

複雑アニメーション管理の問題点を図解します。

mermaidstateDiagram-v2
  [*] --> Loading: データ読み込み開始
  Loading --> Loaded: 非同期完了
  Loaded --> AnimStart: アニメーション開始
  AnimStart --> Element1: 要素1アニメーション
  AnimStart --> Element2: 要素2アニメーション
  AnimStart --> Element3: 要素3アニメーション

  Element1 --> [*]: 完了時期バラバラ
  Element2 --> [*]: 管理が複雑
  Element3 --> [*]: 状態追跡困難

この図で示されるように、個別要素の状態管理が複雑になり、全体の制御が困難になる問題があります。

タイミング制御の困難さ

アニメーションのタイミング制御は、ユーザーエクスペリエンスに直接影響する重要な要素です。しかし、従来の手法では以下のような問題が発生します。

問題内容影響
1手動 delay 計算の必要性要素数変更時の再計算コスト
2タイミング調整の煩雑さ微調整時の修正範囲拡大
3条件分岐の複雑化状態に応じたタイミング変更の困難さ

コードの可読性低下

複雑なアニメーション制御のため、以下のようなコードの問題が発生します。

javascript// 可読性の低いアニメーション制御例
const handleComplexAnimation = async () => {
  const items = document.querySelectorAll('.item');
  const delays = [0, 100, 200, 300, 400]; // ハードコーディング

  for (let i = 0; i < items.length; i++) {
    setTimeout(() => {
      items[i].classList.add('animate');

      if (someCondition && i === items.length - 1) {
        setTimeout(() => {
          // さらにネストした制御
          performAdditionalAnimation();
        }, 300);
      }
    }, delays[i]);
  }
};

このようなコードは理解が困難で、修正時のリスクも高くなります。

従来手法の限界

個別アニメーション管理の煩雑さ

従来のアプローチでは、各要素のアニメーション状態を個別に管理する必要がありました。これにより、以下の問題が発生していました。

javascript// 個別管理による煩雑なコード例
const [item1Visible, setItem1Visible] = useState(false);
const [item2Visible, setItem2Visible] = useState(false);
const [item3Visible, setItem3Visible] = useState(false);

const showItems = () => {
  setItem1Visible(true);
  setTimeout(() => setItem2Visible(true), 100);
  setTimeout(() => setItem3Visible(true), 200);
};

このような実装では、要素数の変更や条件分岐の追加が困難になります。

パフォーマンスの問題

個別管理によるアプローチは、以下のパフォーマンス問題も引き起こします。

  • 再レンダリングの増加: 各要素の状態変更が個別の再レンダリングを誘発
  • メモリ使用量の増大: 要素ごとの状態管理による変数増加
  • 計算コストの上昇: タイミング計算の重複実行

これらの課題を解決するため、Motion Variants の staggerChildrenwhen 機能の活用が重要になります。

解決策

staggerChildren による段階的アニメーション

staggerChildren は、親要素から子要素への段階的なアニメーション実行を可能にする機能です。従来の手動 delay 計算を不要にし、宣言的なアプローチでタイミング制御を実現します。

基本概念と動作原理

staggerChildren の動作原理を以下のコードで確認してみましょう。

typescript// staggerChildrenの基本実装
const containerVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1, // 0.1秒間隔で子要素をアニメーション
      delayChildren: 0.2, // 最初の子要素を0.2秒遅延
    },
  },
};

親要素の Variants 設定により、子要素が自動的に段階実行されます。

typescript// 子要素のVariants定義
const itemVariants = {
  hidden: {
    opacity: 0,
    y: 20,
  },
  visible: {
    opacity: 1,
    y: 0,
    transition: { duration: 0.3 },
  },
};

staggerChildren の動作フローを図で示します。

mermaidsequenceDiagram
  participant Parent as 親コンポーネント
  participant Child1 as 子要素1
  participant Child2 as 子要素2
  participant Child3 as 子要素3

  Parent->>Parent: animate="visible"
  Parent->>Child1: delayChildren(0.2s)後に開始
  Note over Child1: opacity: 0→1, y: 20→0
  Parent->>Child2: +staggerChildren(0.1s)で開始
  Note over Child2: opacity: 0→1, y: 20→0
  Parent->>Child3: +staggerChildren(0.1s)で開始
  Note over Child3: opacity: 0→1, y: 20→0

この図が示すように、親要素の状態変更を起点として、子要素が段階的にアニメーション実行されます。

delayChildren との組み合わせ

delayChildren と組み合わせることで、より柔軟なタイミング制御が可能になります。

typescript// 高度なタイミング制御の実装
const advancedVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      delayChildren: 0.5, // 全体の開始遅延
      staggerChildren: 0.15, // 各要素の間隔
      duration: 0.6, // 親要素のアニメーション時間
      ease: 'easeOut',
    },
  },
};

実装パターン

実用的な実装パターンをいくつか紹介します。

typescript// パターン1: 基本的なリストアニメーション
const ListContainer: React.FC = () => {
  const [isVisible, setIsVisible] = useState(false);

  const containerVariants = {
    hidden: { opacity: 0 },
    visible: {
      opacity: 1,
      transition: {
        staggerChildren: 0.1,
        delayChildren: 0.2,
      },
    },
  };

  return (
    <motion.ul
      variants={containerVariants}
      initial='hidden'
      animate={isVisible ? 'visible' : 'hidden'}
      className='list-container'
    >
      {items.map((item, index) => (
        <motion.li
          key={item.id}
          variants={itemVariants}
          className='list-item'
        >
          {item.content}
        </motion.li>
      ))}
    </motion.ul>
  );
};

when による条件付きアニメーション

when 機能は、特定の条件に基づいてアニメーションの実行タイミングを制御します。複雑な状態遷移や条件分岐を宣言的に表現できる強力な機能です。

条件分岐の実装方法

基本的な when の使用方法を確認しましょう。

typescript// whenによる条件付きアニメーション
const conditionalVariants = {
  hidden: { opacity: 0, scale: 0.8 },
  visible: {
    opacity: 1,
    scale: 1,
    transition: {
      when: 'beforeChildren', // 子要素アニメーション前に実行
      staggerChildren: 0.1,
    },
  },
};

when の主要なオプションとその動作を表で整理します。

when の値実行タイミング使用場面
"beforeChildren"子要素アニメーション前親要素の準備が必要な場合
"afterChildren"子要素アニメーション後子要素完了後の後処理
カスタム条件指定条件満足時複雑な条件分岐制御

複雑な状態遷移の管理

実際のアプリケーションでは、複数の状態を組み合わせた複雑な遷移が必要です。

typescript// 複雑な状態遷移の実装例
const complexVariants = {
  initial: {
    opacity: 0,
    y: 50,
  },
  loading: {
    opacity: 0.5,
    y: 0,
    transition: { duration: 0.3 },
  },
  loaded: {
    opacity: 1,
    y: 0,
    transition: {
      duration: 0.4,
      when: 'beforeChildren',
      staggerChildren: 0.08,
      delayChildren: 0.1,
    },
  },
  error: {
    opacity: 0.7,
    y: 10,
    transition: { duration: 0.2 },
  },
};

状態遷移の制御フローを図解します。

mermaidstateDiagram-v2
  [*] --> initial: コンポーネント初期化
  initial --> loading: データ取得開始
  loading --> loaded: 取得成功
  loading --> error: 取得失敗
  error --> loading: リトライ
  loaded --> loading: 再読み込み

  state loaded {
    [*] --> parent_anim: when="beforeChildren"
    parent_anim --> children_anim: 親アニメーション完了
    children_anim --> [*]: staggerChildren実行
  }

この状態管理により、ユーザーの操作やデータの状態に応じた適切なアニメーション制御が可能になります。

具体例

staggerChildren の実装例

実際の Web アプリケーションで活用される、具体的な staggerChildren の実装例を紹介します。

リストアニメーション

ナビゲーションメニューやタスクリストなど、一般的なリストアニメーションの実装例です。

typescript// リストアニメーションの完全実装
import { motion } from 'framer-motion';
import { useState, useEffect } from 'react';

interface ListItem {
  id: string;
  title: string;
  description: string;
}

const AnimatedList: React.FC = () => {
  const [items, setItems] = useState<ListItem[]>([]);
  const [isVisible, setIsVisible] = useState(false);

  // データ取得の模擬
  useEffect(() => {
    const fetchData = async () => {
      // APIからのデータ取得を想定
      const mockData = [
        {
          id: '1',
          title: 'タスク1',
          description: '重要な作業項目',
        },
        {
          id: '2',
          title: 'タスク2',
          description: '次の優先事項',
        },
        {
          id: '3',
          title: 'タスク3',
          description: '追加の作業内容',
        },
      ];

      setItems(mockData);
      setIsVisible(true);
    };

    fetchData();
  }, []);

  // コンテナのVariants設定
  const containerVariants = {
    hidden: {
      opacity: 0,
    },
    visible: {
      opacity: 1,
      transition: {
        staggerChildren: 0.15, // 各アイテム間の間隔
        delayChildren: 0.3, // 最初のアイテムまでの遅延
      },
    },
  };

  return (
    <motion.div
      variants={containerVariants}
      initial='hidden'
      animate={isVisible ? 'visible' : 'hidden'}
      className='list-wrapper'
    >
      {items.map((item) => (
        <AnimatedListItem key={item.id} item={item} />
      ))}
    </motion.div>
  );
};

リストアイテム個別のアニメーション設定です。

typescript// 個別アイテムのアニメーション設定
const AnimatedListItem: React.FC<{ item: ListItem }> = ({
  item,
}) => {
  const itemVariants = {
    hidden: {
      opacity: 0,
      x: -20,
      scale: 0.95,
    },
    visible: {
      opacity: 1,
      x: 0,
      scale: 1,
      transition: {
        duration: 0.4,
        ease: 'easeOut',
      },
    },
  };

  return (
    <motion.div
      variants={itemVariants}
      whileHover={{ scale: 1.02 }}
      whileTap={{ scale: 0.98 }}
      className='list-item'
    >
      <h3>{item.title}</h3>
      <p>{item.description}</p>
    </motion.div>
  );
};

カードギャラリーのエントランス効果

商品一覧やブログ記事一覧など、カード形式での表示に適したアニメーション実装です。

typescript// カードギャラリーの実装
const CardGallery: React.FC = () => {
  const [cards, setCards] = useState<Card[]>([]);

  const galleryVariants = {
    hidden: {},
    visible: {
      transition: {
        staggerChildren: 0.1,
        delayChildren: 0.2,
      },
    },
  };

  const cardVariants = {
    hidden: {
      opacity: 0,
      y: 60,
      rotateX: -15,
    },
    visible: {
      opacity: 1,
      y: 0,
      rotateX: 0,
      transition: {
        duration: 0.6,
        ease: [0.25, 0.46, 0.45, 0.94], // カスタムeasing
      },
    },
  };

  return (
    <motion.div
      variants={galleryVariants}
      initial='hidden'
      animate='visible'
      className='card-gallery'
    >
      {cards.map((card, index) => (
        <motion.div
          key={card.id}
          variants={cardVariants}
          className='card'
          style={{ zIndex: cards.length - index }} // 重なり順制御
        >
          <img src={card.image} alt={card.title} />
          <div className='card-content'>
            <h3>{card.title}</h3>
            <p>{card.description}</p>
          </div>
        </motion.div>
      ))}
    </motion.div>
  );
};

カードギャラリーのアニメーション流れを図で示します。

mermaidflowchart TD
  start[ギャラリー表示開始] --> delay[delayChildren: 0.2s]
  delay --> card1[カード1アニメーション<br/>y: 60→0, opacity: 0→1]
  card1 --> wait1[staggerChildren: 0.1s待機]
  wait1 --> card2[カード2アニメーション<br/>y: 60→0, opacity: 0→1]
  card2 --> wait2[staggerChildren: 0.1s待機]
  wait2 --> card3[カード3アニメーション<br/>y: 60→0, opacity: 0→1]

  style start fill:#e3f2fd
  style card1 fill:#f3e5f5
  style card2 fill:#f3e5f5
  style card3 fill:#f3e5f5

when を使った条件付きアニメーション

ユーザーインタラクションに応じた切り替え

ユーザーの操作に応じて動的にアニメーションを変更する実装例です。

typescript// インタラクション対応アニメーション
const InteractiveComponent: React.FC = () => {
  const [isExpanded, setIsExpanded] = useState(false);
  const [isLoading, setIsLoading] = useState(false);

  const containerVariants = {
    collapsed: {
      height: 60,
      transition: {
        duration: 0.3,
        when: 'afterChildren', // 子要素アニメーション後に高さ変更
      },
    },
    expanded: {
      height: 'auto',
      transition: {
        duration: 0.4,
        when: 'beforeChildren', // 子要素アニメーション前に高さ変更
        staggerChildren: 0.1,
        delayChildren: 0.1,
      },
    },
  };

  const itemVariants = {
    collapsed: {
      opacity: 0,
      y: -10,
      transition: { duration: 0.2 },
    },
    expanded: {
      opacity: 1,
      y: 0,
      transition: { duration: 0.3 },
    },
  };

  return (
    <motion.div
      variants={containerVariants}
      animate={isExpanded ? 'expanded' : 'collapsed'}
      className='expandable-container'
    >
      <button
        onClick={() => setIsExpanded(!isExpanded)}
        className='toggle-button'
      >
        {isExpanded ? '閉じる' : '開く'}
      </button>

      {/* 展開時のみ表示される子要素 */}
      <motion.div className='content-list'>
        {['項目1', '項目2', '項目3'].map((item, index) => (
          <motion.div
            key={index}
            variants={itemVariants}
            className='content-item'
          >
            {item}
          </motion.div>
        ))}
      </motion.div>
    </motion.div>
  );
};

複数状態の管理

ローディング、エラー、成功など、複数の状態を管理する実装例です。

typescript// 複数状態管理の実装
const MultiStateComponent: React.FC = () => {
  const [status, setStatus] = useState<
    'idle' | 'loading' | 'success' | 'error'
  >('idle');
  const [data, setData] = useState<any[]>([]);

  const containerVariants = {
    idle: {
      opacity: 0.8,
      scale: 0.95,
    },
    loading: {
      opacity: 0.6,
      scale: 1,
      transition: {
        duration: 0.3,
        repeat: Infinity,
        repeatType: 'reverse' as const,
      },
    },
    success: {
      opacity: 1,
      scale: 1,
      transition: {
        duration: 0.4,
        when: 'beforeChildren',
        staggerChildren: 0.08,
        delayChildren: 0.2,
      },
    },
    error: {
      opacity: 0.9,
      scale: 1,
      transition: { duration: 0.3 },
    },
  };

  const handleDataFetch = async () => {
    setStatus('loading');
    try {
      // API呼び出しの模擬
      await new Promise((resolve) =>
        setTimeout(resolve, 2000)
      );
      setData([1, 2, 3, 4, 5]);
      setStatus('success');
    } catch (error) {
      setStatus('error');
    }
  };

  return (
    <motion.div
      variants={containerVariants}
      animate={status}
      className='multi-state-container'
    >
      <button onClick={handleDataFetch}>データ取得</button>

      {status === 'loading' && (
        <div className='loading-indicator'>
          読み込み中...
        </div>
      )}

      {status === 'success' && (
        <motion.div className='success-content'>
          {data.map((item, index) => (
            <motion.div
              key={index}
              variants={{
                success: {
                  opacity: 1,
                  y: 0,
                  transition: { duration: 0.3 },
                },
              }}
              initial={{ opacity: 0, y: 20 }}
              className='data-item'
            >
              アイテム {item}
            </motion.div>
          ))}
        </motion.div>
      )}

      {status === 'error' && (
        <div className='error-message'>
          エラーが発生しました
        </div>
      )}
    </motion.div>
  );
};

組み合わせパターン

staggerChildren + when の高度な使い方

staggerChildrenwhen を組み合わせることで、より複雑で洗練されたアニメーション制御が可能になります。

typescript// 高度な組み合わせパターンの実装
const AdvancedAnimationComponent: React.FC = () => {
  const [activeSection, setActiveSection] = useState<
    'intro' | 'content' | 'footer'
  >('intro');

  const masterVariants = {
    intro: {
      backgroundColor: '#f5f5f5',
      transition: {
        duration: 0.5,
        when: 'beforeChildren',
        staggerChildren: 0.15,
        delayChildren: 0.3,
      },
    },
    content: {
      backgroundColor: '#ffffff',
      transition: {
        duration: 0.4,
        when: 'beforeChildren',
        staggerChildren: 0.1,
        delayChildren: 0.2,
      },
    },
    footer: {
      backgroundColor: '#333333',
      transition: {
        duration: 0.6,
        when: 'afterChildren', // 子要素完了後に背景変更
        staggerChildren: 0.08,
      },
    },
  };

  const sectionVariants = {
    intro: {
      y: 0,
      opacity: 1,
      scale: 1,
      transition: { duration: 0.4, ease: 'easeOut' },
    },
    content: {
      y: -20,
      opacity: 1,
      scale: 1.02,
      transition: { duration: 0.5, ease: 'easeInOut' },
    },
    footer: {
      y: 20,
      opacity: 0.8,
      scale: 0.98,
      transition: { duration: 0.3, ease: 'easeIn' },
    },
  };

  return (
    <motion.div
      variants={masterVariants}
      animate={activeSection}
      className='advanced-container'
    >
      <nav className='section-nav'>
        {['intro', 'content', 'footer'].map((section) => (
          <button
            key={section}
            onClick={() => setActiveSection(section as any)}
            className={
              activeSection === section ? 'active' : ''
            }
          >
            {section}
          </button>
        ))}
      </nav>

      {/* 各セクションの要素 */}
      {['要素1', '要素2', '要素3', '要素4'].map(
        (item, index) => (
          <motion.div
            key={index}
            variants={sectionVariants}
            className='section-element'
          >
            <h3>{item}</h3>
            <p>セクション: {activeSection}</p>
          </motion.div>
        )
      )}
    </motion.div>
  );
};

高度な組み合わせパターンの動作を図解します。

mermaidsequenceDiagram
  participant User as ユーザー
  participant Master as マスターコンテナ
  participant Child1 as 子要素1
  participant Child2 as 子要素2
  participant Child3 as 子要素3

  User->>Master: activeSection変更

  alt when="beforeChildren"の場合
    Master->>Master: 背景色変更完了
    Master->>Child1: delayChildren後に開始
    Master->>Child2: +staggerChildren間隔
    Master->>Child3: +staggerChildren間隔
  else when="afterChildren"の場合
    Master->>Child1: 即座に開始
    Master->>Child2: +staggerChildren間隔
    Master->>Child3: +staggerChildren間隔
    Child3->>Master: 全子要素完了通知
    Master->>Master: 背景色変更実行
  end

この実装により、ユーザーの操作に応じて動的に変化する、高度なアニメーションシステムを構築できます。

実用的な組み合わせパターンとその効果を表で整理します。

パターンwhen 設定staggerChildren効果・用途
順次表示"beforeChildren"0.1-0.15sリスト項目の段階表示
一括非表示"afterChildren"0.05-0.08sメニューの高速折りたたみ
階層表示"beforeChildren"0.2s+親 → 子の明確な階層表現
同期完了"afterChildren"0.1s全要素完了後の後処理

これらのパターンを適切に選択することで、ユーザーにとって直感的で心地よいアニメーション体験を提供できます。

まとめ

Motion(旧 Framer Motion)の staggerChildrenwhen 機能は、複雑なアニメーション制御を宣言的かつ効率的に実現するための強力なツールです。本記事で解説した内容を要点として整理します。

主要なメリット

コードの簡潔性: 従来の個別アニメーション管理と比較して、大幅にコード量を削減できます。手動での delay 計算や状態管理が不要になり、保守性の高いコードを書けるようになります。

パフォーマンスの向上: Variants システムによる統一的な管理により、不要な再レンダリングを削減し、アニメーション処理の効率化を図れます。

拡張性の確保: 要素数の変更や新しいアニメーション追加が容易で、プロダクトの成長に合わせて柔軟に対応できます。

実装時の重要ポイント

  • staggerChildren: 時差制御による段階的アニメーション実現
  • when: 条件分岐制御による柔軟なタイミング制御
  • delayChildren: 全体的な開始遅延による演出効果
  • 組み合わせ活用: 複数機能の組み合わせによる高度な制御

図で理解できる要点

  • Variants システムは親子関係でアニメーション状態を自動伝播
  • staggerChildren により個別 delay 計算が不要に
  • when により条件に応じた実行タイミング制御が可能
  • 状態管理の複雑さを宣言的アプローチで解決

これらの機能を適切に活用することで、ユーザーエクスペリエンスを大幅に向上させる、洗練されたアニメーションシステムを構築できるでしょう。現代の Web アプリケーション開発において、Motion Variants は必須の技術となっています。

関連リンク