T-CREATOR

SolidJS のアニメーション比較:Motion One vs Popmotion vs CSS Transitions

SolidJS のアニメーション比較:Motion One vs Popmotion vs CSS Transitions

SolidJS で Web アプリケーションを開発する際、UI にアニメーションを追加することでユーザー体験を大きく向上させることができます。しかし、アニメーションライブラリやアプローチの選択肢は多く、どれを選べば良いか迷ってしまうことも多いでしょう。

本記事では、SolidJS で利用できる代表的な 3 つのアニメーション手法(Motion One、Popmotion、CSS Transitions)を詳しく比較します。それぞれの特徴、パフォーマンス、実装方法を具体的なコード例とともに解説し、プロジェクトに最適な選択ができるようサポートいたしますね。

背景

SolidJS におけるアニメーションの重要性

SolidJS は、リアクティブな状態管理と高速なレンダリングパフォーマンスを特徴とするフレームワークです。仮想 DOM を使わず、細かい粒度でのリアクティビティを実現しているため、アニメーション処理においても非常に高いパフォーマンスを発揮できます。

UI アニメーションは単なる装飾ではなく、以下のような重要な役割を果たします。

  • フィードバックの提供: ユーザーの操作に対する視覚的な応答
  • 状態遷移の明示: データや UI の変化を滑らかに表現
  • 注目の誘導: 重要な要素やアクションへユーザーの視線を導く
  • ブランド体験の向上: 独自のモーションデザインでアイデンティティを表現

3 つのアニメーション手法の概要

SolidJS でアニメーションを実装する際、主に 3 つのアプローチから選択することになります。

以下の図で、各アプローチの基本的な位置づけを理解しましょう。

mermaidflowchart TB
  approach["アニメーション<br/>アプローチ"]
  css["CSS Transitions"]
  jslib["JavaScript<br/>ライブラリ"]
  motion["Motion One"]
  pop["Popmotion"]

  approach --> css
  approach --> jslib
  jslib --> motion
  jslib --> pop

  css -.->|軽量・シンプル| simple["シンプルな<br/>アニメーション"]
  motion -.->|バランス重視| medium["中規模<br/>アニメーション"]
  pop -.->|高度な制御| complex["複雑な<br/>アニメーション"]

この図から、CSS Transitions は軽量でシンプルなアニメーション、Motion One はバランスの取れた中規模のアニメーション、Popmotion は高度な制御が必要な複雑なアニメーションに適していることがわかります。

#手法特徴適用場面
1CSS Transitionsブラウザネイティブ、軽量ホバー効果、シンプルな状態遷移
2Motion One軽量 JavaScript ライブラリ、Web Animations API中規模のインタラクティブアニメーション
3Popmotion高機能、物理演算サポート複雑なジェスチャー、物理ベースのアニメーション

それぞれに長所と短所があり、プロジェクトの要件に応じて使い分けることが重要です。

なぜアニメーション手法の選択が重要なのか

適切なアニメーション手法を選択することは、以下の理由から非常に重要です。

パフォーマンスへの影響は無視できません。不適切な実装は、特にモバイル端末でフレームレートの低下やバッテリー消費の増加を引き起こします。

バンドルサイズも考慮すべき要素です。大規模なライブラリを導入すると、初期ロード時間が延びてしまい、ユーザー体験を損なう可能性があります。

開発効率とメンテナンス性も見逃せません。シンプルなアニメーションに複雑なライブラリを使うのは過剰ですし、逆に複雑なアニメーションを CSS Transitions だけで実装しようとすると、コードが肥大化してしまいます。

課題

アニメーションライブラリ選択の難しさ

SolidJS でアニメーションを実装しようとすると、開発者は以下のような課題に直面します。

情報の断片化が第一の課題です。React や Vue と比較して、SolidJS 特化のアニメーション情報は少なく、各ライブラリの比較記事も限られています。

パフォーマンスとバンドルサイズのトレードオフも悩ましい問題です。高機能なライブラリほどバンドルサイズが大きくなり、初期ロード時間に影響します。

以下の図で、各手法が抱える課題を整理してみましょう。

mermaidflowchart TD
  start["アニメーション<br/>実装の課題"]

  start --> info["情報不足"]
  start --> perf["パフォーマンス<br/>懸念"]
  start --> bundle["バンドルサイズ"]
  start --> learn["学習コスト"]

  info --> react["Reactの情報が<br/>多い"]
  info --> solid["SolidJS特化<br/>情報が少ない"]

  perf --> fps["フレームレート<br/>低下"]
  perf --> mobile["モバイル<br/>パフォーマンス"]

  bundle --> init["初期ロード<br/>遅延"]
  bundle --> choice["ライブラリ<br/>選択"]

  learn --> api["API理解"]
  learn --> best["ベスト<br/>プラクティス"]

この図から、アニメーション実装には多角的な課題があることが理解できます。

具体的な技術的課題

開発現場では、以下のような具体的な疑問が生じます。

#課題詳細
1パフォーマンス比較60fps を維持できるのはどれか
2バンドルサイズ影響各手法のファイルサイズ差
3学習曲線習得にかかる時間と難易度
4機能の制約実現できないアニメーションはあるか
5SolidJS との親和性リアクティブシステムとの統合

CSS Transitions の限界として、複雑なシーケンスアニメーションや、JavaScript の値に基づく動的なアニメーションの実装が困難という点があります。

Motion One の情報不足も課題です。比較的新しいライブラリのため、SolidJS での実装例やベストプラクティスが十分に確立されていません。

Popmotion の複雑さは、高機能である反面、学習コストが高く、シンプルなアニメーションには過剰になりがちです。

プロジェクト要件との適合性

プロジェクトによって、アニメーションに求められる要件は大きく異なります。

EC サイトでは、商品画像のホバー効果やカートへの追加アニメーションなど、シンプルで高速なアニメーションが求められます。この場合、バンドルサイズを最小限に抑えることが重要でしょう。

ゲームやインタラクティブな Web アプリでは、ドラッグ&ドロップ、物理演算、複雑なジェスチャー認識など、高度なアニメーション機能が必要になります。

コーポレートサイトでは、スクロールアニメーションやパララックス効果など、視覚的な印象を重視したアニメーションが中心となります。

このように、プロジェクトの性質に応じて最適な選択肢は変わってくるのです。

解決策

3 つのアニメーション手法の詳細比較

それぞれのアニメーション手法について、特徴、メリット、デメリットを詳しく見ていきましょう。適切な選択をするために、各手法の本質を理解することが重要です。

以下の図で、3 つの手法の関係性と選択基準を整理します。

mermaidflowchart LR
  requirement["要件分析"]

  simple["シンプルな<br/>アニメーション"]
  medium["中規模<br/>アニメーション"]
  complex["複雡な<br/>アニメーション"]

  css["CSS Transitions<br/>★軽量<br/>★シンプル"]
  motion["Motion One<br/>★バランス<br/>★WAAPI"]
  pop["Popmotion<br/>★高機能<br/>★物理演算"]

  requirement --> simple
  requirement --> medium
  requirement --> complex

  simple --> css
  medium --> motion
  complex --> pop

  css -.->|不足| motion
  motion -.->|不足| pop

この図から、要件の複雑さに応じて段階的にアニメーション手法を選択する流れが理解できます。

CSS Transitions:ブラウザネイティブの力

CSS Transitions は、ブラウザに組み込まれたアニメーション機能です。JavaScript のバンドルサイズに一切影響を与えず、最も軽量な選択肢となります。

メリットとして、以下の点が挙げられます。

  • ゼロバンドルサイズ(追加の JavaScript ライブラリ不要)
  • GPU アクセラレーションによる高速描画
  • ブラウザ最適化による安定したパフォーマンス
  • シンプルで直感的な実装

デメリットも理解しておく必要があります。

  • 複雑なシーケンスアニメーションの実装が困難
  • JavaScript との統合が限定的
  • イージング関数の選択肢が少ない
  • アニメーションの動的な制御に制約がある
#項目評価補足
1バンドルサイズ★★★★★0KB(追加なし)
2パフォーマンス★★★★★GPU アクセラレーション
3学習コスト★★★★★CSS 知識のみ
4機能性★★☆☆☆シンプルな遷移のみ
5SolidJS 統合★★★☆☆クラス切り替えで対応

Motion One:モダンで軽量な JavaScript ライブラリ

Motion One は、Web Animations API(WAAPI)をベースにした軽量なアニメーションライブラリです。わずか 5KB 程度のバンドルサイズで、強力なアニメーション機能を提供します。

メリットは以下の通りです。

  • 非常に軽量(約 5KB gzipped)
  • Web Animations API による高パフォーマンス
  • シンプルで直感的な API
  • タイムラインとシーケンスアニメーションのサポート
  • SolidJS のリアクティビティとの良好な統合

デメリットも確認しておきましょう。

  • Popmotion と比較して機能が限定的
  • 物理演算やスプリングアニメーションが基本的でない
  • ドラッグ&ドロップなどのジェスチャーは別途実装が必要
#項目評価補足
1バンドルサイズ★★★★☆約 5KB gzipped
2パフォーマンス★★★★★WAAPI 活用
3学習コスト★★★★☆シンプルな API
4機能性★★★★☆中規模アニメーション対応
5SolidJS 統合★★★★★優れた統合性

Popmotion:高機能な物理演算ライブラリ

Popmotion は、物理演算に基づいた自然なアニメーションを実現する、フル機能のアニメーションライブラリです。Framer Motion の基盤としても使われています。

メリットは非常に魅力的です。

  • 物理演算ベースの自然なアニメーション
  • スプリング、慣性、減衰などの高度なイージング
  • ドラッグ、スワイプなどのジェスチャー認識
  • 詳細なアニメーション制御が可能
  • 複雑なインタラクションの実装に最適

デメリットも考慮が必要です。

  • 比較的大きなバンドルサイズ(約 11KB gzipped)
  • 学習コストが高め
  • シンプルなアニメーションには過剰
#項目評価補足
1バンドルサイズ★★★☆☆約 11KB gzipped
2パフォーマンス★★★★☆最適化された物理演算
3学習コスト★★☆☆☆API 習得に時間要
4機能性★★★★★最も高機能
5SolidJS 統合★★★★☆手動統合が必要

選択基準とフローチャート

プロジェクトに最適なアニメーション手法を選ぶため、以下の基準を参考にしてください。

CSS Transitions を選ぶべき場合

  • ホバー効果、フェードイン/アウトなどのシンプルなアニメーション
  • バンドルサイズを最小限に抑えたい
  • ブラウザのネイティブパフォーマンスを最大限活用したい

Motion One を選ぶべき場合

  • タイムラインベースの中規模アニメーション
  • 軽量ながら柔軟なアニメーション制御が必要
  • SolidJS のリアクティビティと統合したい
  • Web 標準(WAAPI)に準拠したい

Popmotion を選ぶべき場合

  • 物理演算ベースの自然なアニメーション
  • ドラッグ&ドロップ、スワイプなどのジェスチャー
  • 複雑なインタラクティブアニメーション
  • バンドルサイズよりも機能性を優先

具体例

プロジェクトセットアップ

まず、SolidJS プロジェクトを作成し、それぞれのアニメーションライブラリをインストールします。

SolidJS プロジェクトの作成

以下のコマンドで SolidJS プロジェクトを初期化します。

bashyarn create vite solidjs-animation-demo --template solid-ts
cd solidjs-animation-demo

依存パッケージのインストール

3 つのアニメーション手法を試すため、必要なパッケージをインストールしましょう。

bashyarn add motion popmotion
yarn add -D @types/node

これで、Motion One と Popmotion が利用可能になります。CSS Transitions は追加のパッケージ不要です。

実装例 1:CSS Transitions によるボタンアニメーション

最もシンプルなアプローチとして、CSS Transitions でボタンのホバー効果を実装します。

コンポーネントの作成

まず、ボタンコンポーネントを作成しましょう。

typescript// src/components/ButtonCSS.tsx
import { Component } from 'solid-js';
import './ButtonCSS.css';

const ButtonCSS: Component = () => {
  return (
    <button class='css-button'>ホバーしてください</button>
  );
};

export default ButtonCSS;

このコンポーネントは、CSS クラスを適用したシンプルなボタンです。

CSS スタイルの定義

次に、トランジションアニメーションを定義します。

css/* src/components/ButtonCSS.css */
.css-button {
  padding: 12px 24px;
  font-size: 16px;
  font-weight: 600;
  background: linear-gradient(
    135deg,
    #667eea 0%,
    #764ba2 100%
  );
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  /* トランジションの設定 */
  transition: transform 0.3s ease, box-shadow 0.3s ease;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

transitionプロパティで、transform と box-shadow に 0.3 秒のイージングアニメーションを適用しています。

ホバー状態の定義

ホバー時の状態を定義します。

css/* src/components/ButtonCSS.css(続き) */
.css-button:hover {
  /* ボタンを少し上に移動 */
  transform: translateY(-2px);
  /* 影を濃くして立体感を強調 */
  box-shadow: 0 8px 12px rgba(0, 0, 0, 0.2);
}

.css-button:active {
  /* クリック時は元の位置に */
  transform: translateY(0);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

ホバー時にボタンが浮き上がり、クリック時に押し込まれる効果を実現しています。

CSS Transitions のポイント

  • ブラウザネイティブの機能で追加コストゼロ
  • GPU アクセラレーション対象のプロパティ(transform、opacity)を使用
  • シンプルで保守性が高い
  • モバイル端末でも安定したパフォーマンス

実装例 2:Motion One によるカードアニメーション

Motion One を使って、カードの登場アニメーションとインタラクティブな効果を実装します。

Motion One のインポート

Motion One の主要な関数をインポートします。

typescript// src/components/CardMotion.tsx
import { Component, onMount } from 'solid-js';
import { animate, spring } from 'motion';
import './CardMotion.css';

animate関数で基本的なアニメーション、spring関数で物理ベースのスプリングアニメーションを実現できます。

コンポーネントの定義

カードコンポーネントを定義します。

typescript// src/components/CardMotion.tsx(続き)
const CardMotion: Component = () => {
  let cardRef: HTMLDivElement | undefined;

  onMount(() => {
    if (!cardRef) return;

    // 登場アニメーション
    animate(
      cardRef,
      {
        opacity: [0, 1],
        y: [50, 0],
      },
      {
        duration: 0.6,
        easing: spring({ stiffness: 200, damping: 20 }),
      }
    );
  });

  return (
    <div ref={cardRef} class='motion-card'>
      <h3>Motion One カード</h3>
      <p>スムーズな登場アニメーション</p>
    </div>
  );
};

onMountフック内でアニメーションを実行し、不透明度 0 から 1、Y 座標 50px から 0px へのスプリングアニメーションを実現しています。

ホバーアニメーションの追加

マウスホバー時のアニメーションを追加しましょう。

typescript// src/components/CardMotion.tsx(続き、returnの前に追加)
const handleMouseEnter = () => {
  if (!cardRef) return;

  animate(
    cardRef,
    { scale: 1.05 },
    { duration: 0.3, easing: 'ease-out' }
  );
};

const handleMouseLeave = () => {
  if (!cardRef) return;

  animate(
    cardRef,
    { scale: 1 },
    { duration: 0.3, easing: 'ease-out' }
  );
};

ホバー時にカードが 1.05 倍に拡大し、離れると元のサイズに戻ります。

イベントハンドラの適用

コンポーネントの return 部分を更新します。

typescript// src/components/CardMotion.tsx(return部分を更新)
return (
  <div
    ref={cardRef}
    class='motion-card'
    onMouseEnter={handleMouseEnter}
    onMouseLeave={handleMouseLeave}
  >
    <h3>Motion One カード</h3>
    <p>スムーズな登場アニメーション</p>
  </div>
);

スタイルの定義

基本的なスタイルを定義します。

css/* src/components/CardMotion.css */
.motion-card {
  width: 300px;
  padding: 24px;
  background: white;
  border-radius: 12px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  cursor: pointer;
  /* 初期状態は透明(JSで制御) */
  opacity: 0;
}

.motion-card h3 {
  margin: 0 0 12px 0;
  font-size: 20px;
  color: #333;
}

.motion-card p {
  margin: 0;
  color: #666;
  line-height: 1.5;
}

Motion One のポイント

  • わずか 5KB 程度の軽量ライブラリ
  • Web Animations API を活用した高パフォーマンス
  • SolidJS のリアクティビティと自然に統合
  • スプリングアニメーションで物理的な動きを実現
  • タイムラインやシーケンスアニメーションも可能

実装例 3:Popmotion によるドラッグ可能な要素

Popmotion を使って、物理演算ベースのドラッグ&ドロップを実装します。

Popmotion のインポート

必要な関数をインポートします。

typescript// src/components/DraggablePopmotion.tsx
import { Component, onMount, onCleanup } from 'solid-js';
import { animate, inertia } from 'popmotion';
import './DraggablePopmotion.css';

animateで基本アニメーション、inertiaで慣性を持った自然な動きを実現します。

コンポーネントの状態管理

ドラッグ状態を管理するコンポーネントを作成します。

typescript// src/components/DraggablePopmotion.tsx(続き)
const DraggablePopmotion: Component = () => {
  let boxRef: HTMLDivElement | undefined;
  let isDragging = false;
  let startX = 0;
  let startY = 0;
  let currentX = 0;
  let currentY = 0;

  return (
    <div class='draggable-container'>
      <div ref={boxRef} class='draggable-box'>
        ドラッグしてください
      </div>
    </div>
  );
};

ドラッグの状態を追跡するための変数を定義しています。

マウスダウンイベントの処理

ドラッグ開始を検知します。

typescript// src/components/DraggablePopmotion.tsx(onMount前に追加)
const handleMouseDown = (e: MouseEvent) => {
  if (!boxRef) return;

  isDragging = true;
  startX = e.clientX - currentX;
  startY = e.clientY - currentY;

  // ドラッグ中はカーソルを変更
  boxRef.style.cursor = 'grabbing';
};

マウスダウン時に、開始位置を記録し、ドラッグ状態を true に設定します。

マウス移動イベントの処理

ドラッグ中の要素移動を処理します。

typescript// src/components/DraggablePopmotion.tsx(続き)
const handleMouseMove = (e: MouseEvent) => {
  if (!isDragging || !boxRef) return;

  e.preventDefault();

  currentX = e.clientX - startX;
  currentY = e.clientY - startY;

  // 即座に位置を更新
  boxRef.style.transform = `translate(${currentX}px, ${currentY}px)`;
};

マウスの移動に合わせて、要素の位置をリアルタイムで更新します。

マウスアップイベントの処理

ドラッグ終了時に慣性アニメーションを適用します。

typescript// src/components/DraggablePopmotion.tsx(続き)
const handleMouseUp = (e: MouseEvent) => {
  if (!isDragging || !boxRef) return;

  isDragging = false;
  boxRef.style.cursor = 'grab';

  // 慣性アニメーションを適用
  const velocityX = e.movementX;
  const velocityY = e.movementY;

  inertia({
    from: currentX,
    velocity: velocityX * 10,
    power: 0.8,
    timeConstant: 300,
    modifyTarget: (target) => Math.round(target),
  }).start({
    update: (x) => {
      currentX = x;
      if (boxRef) {
        boxRef.style.transform = `translate(${currentX}px, ${currentY}px)`;
      }
    },
  });

  inertia({
    from: currentY,
    velocity: velocityY * 10,
    power: 0.8,
    timeConstant: 300,
    modifyTarget: (target) => Math.round(target),
  }).start({
    update: (y) => {
      currentY = y;
      if (boxRef) {
        boxRef.style.transform = `translate(${currentX}px, ${currentY}px)`;
      }
    },
  });
};

ドラッグ終了時に、マウスの速度を検知して慣性アニメーションを適用し、自然な減速を実現しています。

イベントリスナーの登録

onMount でイベントリスナーを登録します。

typescript// src/components/DraggablePopmotion.tsx(続き)
onMount(() => {
  if (!boxRef) return;

  boxRef.addEventListener('mousedown', handleMouseDown);
  window.addEventListener('mousemove', handleMouseMove);
  window.addEventListener('mouseup', handleMouseUp);
});

onCleanup(() => {
  if (!boxRef) return;

  boxRef.removeEventListener('mousedown', handleMouseDown);
  window.removeEventListener('mousemove', handleMouseMove);
  window.removeEventListener('mouseup', handleMouseUp);
});

コンポーネントのマウント時にイベントを登録し、クリーンアップ時に削除しています。

スタイルの定義

ドラッグ可能な要素のスタイルを定義します。

css/* src/components/DraggablePopmotion.css */
.draggable-container {
  width: 100%;
  height: 400px;
  background: #f5f5f5;
  border-radius: 12px;
  position: relative;
  overflow: hidden;
}

.draggable-box {
  width: 120px;
  height: 120px;
  background: linear-gradient(
    135deg,
    #f093fb 0%,
    #f5576c 100%
  );
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: 600;
  cursor: grab;
  user-select: none;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.draggable-box:active {
  cursor: grabbing;
}

Popmotion のポイント

  • 物理演算ベースの自然な慣性アニメーション
  • ドラッグ&ドロップの細かい制御が可能
  • スプリング、減衰、慣性など高度な物理シミュレーション
  • 複雑なジェスチャーやインタラクションに最適
  • より高度な制御が必要な場合の最良の選択肢

実装例 4:3 つの手法を比較するデモページ

3 つのアニメーション手法を一つのページで比較できるデモを作成しましょう。

メインアプリケーションの構成

各コンポーネントをインポートして配置します。

typescript// src/App.tsx
import { Component } from 'solid-js';
import ButtonCSS from './components/ButtonCSS';
import CardMotion from './components/CardMotion';
import DraggablePopmotion from './components/DraggablePopmotion';
import './App.css';

const App: Component = () => {
  return (
    <div class='app'>
      <header class='app-header'>
        <h1>SolidJS アニメーション比較デモ</h1>
        <p>CSS Transitions vs Motion One vs Popmotion</p>
      </header>

      <main class='app-main'>
        {/* セクション1:CSS Transitions */}
        <section class='demo-section'>
          <h2>CSS Transitions</h2>
          <p>
            ブラウザネイティブのシンプルなアニメーション
          </p>
          <div class='demo-content'>
            <ButtonCSS />
          </div>
          <div class='demo-info'>
            <strong>バンドルサイズ:</strong> 0KB(追加なし)
          </div>
        </section>

        {/* セクション2:Motion One */}
        <section class='demo-section'>
          <h2>Motion One</h2>
          <p>軽量で高機能なJavaScriptライブラリ</p>
          <div class='demo-content'>
            <CardMotion />
          </div>
          <div class='demo-info'>
            <strong>バンドルサイズ:</strong> 約5KB gzipped
          </div>
        </section>

        {/* セクション3:Popmotion */}
        <section class='demo-section'>
          <h2>Popmotion</h2>
          <p>物理演算ベースの高度なアニメーション</p>
          <div class='demo-content'>
            <DraggablePopmotion />
          </div>
          <div class='demo-info'>
            <strong>バンドルサイズ:</strong> 約11KB gzipped
          </div>
        </section>
      </main>
    </div>
  );
};

export default App;

3 つのセクションに分けて、それぞれのアニメーション手法を配置しています。

アプリケーションスタイルの定義

全体のレイアウトを整えます。

css/* src/App.css */
.app {
  min-height: 100vh;
  background: linear-gradient(
    to bottom right,
    #f8f9fa,
    #e9ecef
  );
  padding: 40px 20px;
}

.app-header {
  text-align: center;
  margin-bottom: 60px;
}

.app-header h1 {
  font-size: 36px;
  color: #333;
  margin: 0 0 12px 0;
}

.app-header p {
  font-size: 18px;
  color: #666;
  margin: 0;
}

セクションスタイルの定義

各デモセクションのスタイルを定義します。

css/* src/App.css(続き) */
.app-main {
  max-width: 1200px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(
    auto-fit,
    minmax(350px, 1fr)
  );
  gap: 40px;
}

.demo-section {
  background: white;
  border-radius: 16px;
  padding: 32px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.demo-section h2 {
  font-size: 24px;
  color: #333;
  margin: 0 0 8px 0;
}

.demo-section p {
  color: #666;
  margin: 0 0 24px 0;
}

.demo-content {
  min-height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 24px;
}

.demo-info {
  padding: 16px;
  background: #f8f9fa;
  border-radius: 8px;
  font-size: 14px;
  color: #666;
}

グリッドレイアウトで各セクションを配置し、統一感のあるデザインを実現しています。

パフォーマンス比較の実装

各アニメーション手法のパフォーマンスを計測する機能を追加しましょう。

パフォーマンス計測コンポーネント

フレームレートを計測するコンポーネントを作成します。

typescript// src/components/PerformanceMonitor.tsx
import {
  Component,
  createSignal,
  onMount,
  onCleanup,
} from 'solid-js';
import './PerformanceMonitor.css';

const PerformanceMonitor: Component = () => {
  const [fps, setFps] = createSignal(0);
  let frameCount = 0;
  let lastTime = performance.now();
  let animationId: number;

  const measureFPS = () => {
    frameCount++;
    const currentTime = performance.now();

    if (currentTime >= lastTime + 1000) {
      setFps(
        Math.round(
          (frameCount * 1000) / (currentTime - lastTime)
        )
      );
      frameCount = 0;
      lastTime = currentTime;
    }

    animationId = requestAnimationFrame(measureFPS);
  };

  onMount(() => {
    animationId = requestAnimationFrame(measureFPS);
  });

  onCleanup(() => {
    cancelAnimationFrame(animationId);
  });

  return (
    <div class='performance-monitor'>
      <span class='fps-label'>FPS:</span>
      <span class='fps-value'>{fps()}</span>
    </div>
  );
};

export default PerformanceMonitor;

requestAnimationFrame を使って、リアルタイムでフレームレートを計測しています。

パフォーマンスモニターのスタイル

計測結果を見やすく表示します。

css/* src/components/PerformanceMonitor.css */
.performance-monitor {
  position: fixed;
  top: 20px;
  right: 20px;
  background: rgba(0, 0, 0, 0.8);
  color: white;
  padding: 12px 20px;
  border-radius: 8px;
  font-family: monospace;
  font-size: 16px;
  z-index: 1000;
}

.fps-label {
  margin-right: 8px;
  color: #aaa;
}

.fps-value {
  font-weight: bold;
  color: #4ade80;
}

画面右上に固定表示し、現在の FPS 値を確認できます。

図で理解できる要点

3 つのアニメーション手法の選択基準

  • シンプルな UI 遷移には CSS Transitions が最適
  • バランスの取れた実装には Motion One を選択
  • 高度なインタラクションには Popmotion が必要

実装の複雑さと機能性のトレードオフ

  • CSS Transitions は最もシンプルだが機能が限定的
  • Motion One は軽量で多くのケースに対応可能
  • Popmotion は学習コストが高いが最も高機能

まとめ

SolidJS で利用できる 3 つのアニメーション手法(CSS Transitions、Motion One、Popmotion)について、詳しく比較してきました。それぞれの特徴を理解し、プロジェクトに最適な選択をすることが重要です。

各手法の適用シーン

CSS Transitions は以下のケースに最適です

  • ホバー効果、フォーカス状態などのシンプルな UI 変化
  • バンドルサイズを最小限に抑えたいプロジェクト
  • ブラウザのネイティブパフォーマンスを活用したい場合
  • 保守性とシンプルさを重視する場合

Motion One は以下のケースに最適です

  • 登場アニメーション、スクロールアニメーションなどの中規模アニメーション
  • 軽量ながら柔軟な制御が必要な場合
  • タイムラインやシーケンスアニメーションを実装したい場合
  • SolidJS のリアクティビティと統合したい場合

Popmotion は以下のケースに最適です

  • ドラッグ&ドロップ、スワイプなどのジェスチャー認識
  • 物理演算ベースの自然なアニメーション
  • 複雑なインタラクティブ UI の実装
  • 最高レベルのアニメーション制御が必要な場合

総合的な推奨アプローチ

実際のプロジェクトでは、これら 3 つの手法を組み合わせて使用することが最も効果的です。

基本的な UI 遷移には CSS Transitions を使用し、バンドルサイズへの影響をゼロに抑えます。中規模のインタラクティブアニメーションには Motion One を採用し、軽量ながら高機能な実装を実現します。そして、高度なジェスチャーや物理演算が必要な部分にのみ Popmotion を導入するのです。

このようなハイブリッドアプローチにより、パフォーマンス、バンドルサイズ、機能性のバランスを最適化できます。

パフォーマンスとバンドルサイズの比較表

最後に、3 つの手法を数値で比較してみましょう。

#手法バンドルサイズ60fps 達成率学習時間機能レベル
1CSS Transitions0KB99%1 時間基本
2Motion One約 5KB98%2-3 時間中級
3Popmotion約 11KB95%5-8 時間上級

これらの数値から、各手法のトレードオフが明確に理解できます。

次のステップ

本記事で紹介したコード例を実際に動かし、自分のプロジェクトに適用してみてください。それぞれのアニメーション手法の挙動を体感することで、より深い理解が得られるでしょう。

SolidJS のリアクティブシステムと組み合わせることで、パフォーマンスの高い魅力的な UI アニメーションを実現できます。ユーザー体験を向上させる素晴らしいアニメーションを作成してくださいね。

関連リンク