0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MotionKitで学ぶモダンアニメーション入門(React向け)

Posted at

はじめに

ここでは、React向けのアニメーションUIコンポーネントライブラリ「MotionKit」を題材に、やさしい日本語でモーションUIの考え方と実践コードをまとめます。
ボタンやカード、モーダルなどのインタラクションに自然な動きを与えたい人向けに、基本概念から応用パターンまで15章構成で解説します。


第1章 MotionKitとは

MotionKitは、Reactアプリケーション向けに設計されたモダンなアニメーション付きUIコンポーネントのコレクションで、汎用的なボタンやカード、レイアウトなどにスムーズなトランジションを提供します。
フレームワークに依存しないプレーンなReactコンポーネントとして実装されているため、Next.jsやViteなどさまざまな環境で再利用できるのが大きな特徴です。

// 基本的なインポート例
import {MotionButton, MotionCard} from '@jekyll-studio/motionkit';

export const Example = () => (
  <MotionCard>
    <MotionButton>クリック</MotionButton>
  </MotionCard>
);

第2章 インストールとセットアップ

MotionKitはnpmレジストリにパッケージとして公開されているので、通常のReactライブラリと同じくnpmやyarnで簡単にインストールできます。
TypeScript型定義も同梱されているため、型安全な開発スタイルと相性が良く、エディタの補完を活かしながらコンポーネント名やプロップスを探索できます。

# npmでのインストール
npm install @jekyll-studio/motionkit

# またはyarn
yarn add @jekyll-studio/motionkit
// src/main.tsx などでグローバルスタイルと併用
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')!).render(<App />);

第3章 基本コンポーネントの使い方

MotionKitには、ホバーやクリック時に自然なアニメーションが付いたボタンやカードなど、すぐに使える基本コンポーネント群が用意されています。
これらは既にアニメーションとスタイルが組み込まれているため、一つひとつCSSトランジションを書くことなく、アプリ全体の見た目と動きを素早く統一できます。

import {MotionButton, MotionCard} from '@jekyll-studio/motionkit';

export const BasicComponentsDemo = () => {
  return (
    <div style={{display: 'flex', gap: 24, padding: 24}}>
      <MotionCard>
        <h2>カードタイトル</h2>
        <p>ホバーすると少し浮き上がるカード。</p>
        <MotionButton appearance="primary">詳しく見る</MotionButton>
      </MotionCard>

      <MotionButton appearance="ghost">二次アクション</MotionButton>
    </div>
  );
};

第4章 プロップスとモーションのカスタマイズ

各コンポーネントは、見た目やアニメーション挙動を制御するためのプロップスを持っており、variantdurationなどを指定することで印象を大きく変えられます。
チームのデザインガイドラインに合わせて色や角丸、シャドウの有無を統一しつつ、軽快なモーションを共通設定として管理できる点が、デザインシステム構築にも向いています。

import {MotionButton} from '@jekyll-studio/motionkit';

export const CustomMotionButton = () => {
  return (
    <div style={{display: 'flex', gap: 16}}>
      <MotionButton
        appearance="primary"
        motion={{duration: 0.25, stiffness: 220}}
      >
        きびきび動く
      </MotionButton>

      <MotionButton
        appearance="primary"
        motion={{duration: 0.5, damping: 18}}
      >
        ふわっと動く
      </MotionButton>
    </div>
  );
};

第5章 状態に応じたアニメーション制御

Reactの状態管理と組み合わせることで、開閉するパネルやオンオフ切り替えボタンなど、状態遷移に応じて自然なアニメーションを付与できます。
Booleanフラグをトリガーとして、表示・非表示やサイズの変化をアニメーションさせることで、ユーザーに現在の状態変化をわかりやすく伝えられます。

import {useState} from 'react';
import {MotionCard, MotionButton} from '@jekyll-studio/motionkit';

export const TogglePanel = () => {
  const [open, setOpen] = useState(false);

  return (
    <div style={{padding: 24}}>
      <MotionButton onClick={() => setOpen(!open)}>
        {open ? '閉じる' : '開く'}
      </MotionButton>

      {open && (
        <MotionCard
          motion={{initial: {opacity: 0, y: -10}, animate: {opacity: 1, y: 0}}}
          style={{marginTop: 16}}
        >
          <p>トグルで開閉するパネル。状態に応じてふわっと表示されます。</p>
        </MotionCard>
      )}
    </div>
  );
};

第6章 レイアウトとレスポンシブデザイン

多くのUIアニメーションはレイアウト変化とセットで起こるため、MotionKitではFlexboxやGridと組み合わせたレイアウトコンポーネントでもスムーズなトランジションを表現できます。
ウィンドウ幅やブレークポイントに応じてカード数や並び順が変わる場面でも、位置の変化がアニメーションされると、ユーザーは変化を追いやすく、迷いを軽減できます。

import {MotionCard} from '@jekyll-studio/motionkit';

const items = ['A', 'B', 'C', 'D'];

export const ResponsiveGrid = () => {
  return (
    <div
      style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))',
        gap: 16,
        padding: 24,
      }}
    >
      {items.map((label) => (
        <MotionCard
          key={label}
          motion={{layout: true}}
          style={{minHeight: 100}}
        >
          <h3>カード {label}</h3>
        </MotionCard>
      ))}
    </div>
  );
};

第7章 ジェスチャーとユーザー入力

MotionKitは、ホバーやクリックといった基本的な入力だけでなく、ドラッグや長押しなどのジェスチャーを取り入れたアニメーションにも応用できます。
ユーザー操作に対して実際の物理挙動に近い動きを返すことで、操作感が直感的になり、単なる見た目以上の体験価値をアプリにもたらします。

import {useState} from 'react';
import {MotionCard} from '@jekyll-studio/motionkit';

export const DraggableCard = () => {
  const [pressed, setPressed] = useState(false);

  return (
    <MotionCard
      motion={{
        whileHover: {scale: 1.02},
        whileTap: {scale: 0.97},
        drag: true,
      }}
      onMouseDown={() => setPressed(true)}
      onMouseUp={() => setPressed(false)}
      style={{
        width: 240,
        height: 140,
        cursor: 'grab',
        border: pressed ? '2px solid #3b82f6' : '1px solid #e5e7eb',
      }}
    >
      <p>ドラッグして動かせるカード。タップ時は少し縮みます。</p>
    </MotionCard>
  );
};

第8章 タイムラインとシーケンス的な表現

単発のトランジションだけでなく、複数の要素を時間差で出現させる「シーケンス」風の表現を行うことで、ステップガイドやオンボーディングが分かりやすくなります。
MotionKitのモーション設定を配列やユーティリティ関数で管理し、遅延時間や順番をパターン化すると、演出の統一感と保守性が向上します。

import {MotionCard} from '@jekyll-studio/motionkit';

const steps = ['ようこそ', '基本操作', '通知設定', '完了!'];

export const OnboardingSequence = () => {
  return (
    <div style={{padding: 32, display: 'flex', gap: 16}}>
      {steps.map((label, index) => (
        <MotionCard
          key={label}
          motion={{
            initial: {opacity: 0, y: 10},
            animate: {opacity: 1, y: 0},
            transition: {delay: index * 0.15},
          }}
        >
          <h4>{label}</h4>
        </MotionCard>
      ))}
    </div>
  );
};

第9章 ダークモードやテーマとの連携

アニメーション付きコンポーネントは、ライトテーマ・ダークテーマなど複数のテーマを切り替えるときにも、色や背景の変化を滑らかにつなぐ役割を果たします。
MotionKitコンポーネントにテーマ名をプロップスとして渡し、CSS変数やコンテキストから色設定を切り替えることで、全画面を一瞬で「雰囲気のある」トランジションに変えられます。

import {useState} from 'react';
import {MotionCard, MotionButton} from '@jekyll-studio/motionkit';

export const ThemedLayout = () => {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  const bg = theme === 'light' ? '#f9fafb' : '#020617';
  const fg = theme === 'light' ? '#111827' : '#e5e7eb';

  return (
    <div style={{minHeight: '100vh', backgroundColor: bg, color: fg}}>
      <div style={{padding: 24}}>
        <MotionButton onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
          テーマ切替
        </MotionButton>

        <MotionCard
          motion={{layout: true}}
          style={{marginTop: 24, padding: 24}}
        >
          <h2>現在のテーマ: {theme}</h2>
          <p>背景や文字色がスムーズに変化するテーマ切り替え。</p>
        </MotionCard>
      </div>
    </div>
  );
};

第10章 入力フォームとバリデーションのフィードバック

フォーム画面では、エラーや成功メッセージをアニメーション付きで表示することで、ユーザーに何が起きたかを素早く、かつストレスなく伝えることができます。
MotionKitのカードやアラートコンポーネントを使って、送信時のローディングや完了トーストを表示すれば、システムの応答性が高く感じられます。

import {useState} from 'react';
import {MotionCard, MotionButton} from '@jekyll-studio/motionkit';

export const AnimatedForm = () => {
  const [submitted, setSubmitted] = useState(false);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    setSubmitted(true);
  };

  return (
    <MotionCard style={{padding: 24, maxWidth: 360}}>
      <form onSubmit={handleSubmit}>
        <label>
          メールアドレス
          <input type="email" required style={{display: 'block', width: '100%'}} />
        </label>
        <MotionButton type="submit" style={{marginTop: 16}}>
          送信
        </MotionButton>
      </form>

      {submitted && (
        <MotionCard
          motion={{
            initial: {opacity: 0, y: 8},
            animate: {opacity: 1, y: 0},
          }}
          style={{marginTop: 16, padding: 12, backgroundColor: '#dcfce7'}}
        >
          <p>送信が完了しました。ご入力ありがとうございます。</p>
        </MotionCard>
      )}
    </MotionCard>
  );
};

第11章 ナビゲーションとページ遷移アニメーション

シングルページアプリケーションでは、ルーティングによるページ切り替えにアニメーションを加えると、画面遷移が滑らかに感じられ、コンテンツ間のつながりも明確になります。
MotionKitコンポーネントをルートコンポーネントとしてラップし、URL変更時にフェードイン・フェードアウトを行うと、SPA特有の「瞬間切替」の違和感を軽減可能です。

import {useLocation} from 'react-router-dom';
import {MotionCard} from '@jekyll-studio/motionkit';

export const AnimatedRouteContainer: React.FC<{children: React.ReactNode}> = ({
  children,
}) => {
  const location = useLocation();

  return (
    <MotionCard
      key={location.pathname}
      motion={{
        initial: {opacity: 0, x: 20},
        animate: {opacity: 1, x: 0},
        exit: {opacity: 0, x: -20},
      }}
      style={{padding: 24, minHeight: '60vh'}}
    >
      {children}
    </MotionCard>
  );
};

第12章 デザインシステムとしての活用

MotionKitを単なるコンポーネント集ではなく「モーション付きデザインシステム」として捉えると、ブランドの世界観をアプリ全体で一貫して表現できます。
トークン化した色や余白、モーション設定をラップする独自コンポーネントを作り、チームメンバーには統一インターフェースだけを使ってもらう運用が効果的です。

// プロジェクト固有のラッパー
import {MotionButton as BaseButton} from '@jekyll-studio/motionkit';

type AppButtonProps = React.ComponentProps<typeof BaseButton>;

export const AppButton: React.FC<AppButtonProps> = (props) => (
  <BaseButton
    appearance="primary"
    motion={{duration: 0.25, stiffness: 230}}
    style={{
      borderRadius: 9999,
      paddingInline: 20,
      ...props.style,
    }}
    {...props}
  />
);

第13章 アクセシビリティとモーションの配慮

アニメーションは便利な一方で、人によっては動きが多すぎると疲れやすくなるため、OSやブラウザの「簡易モーション設定」を尊重した実装が重要です。
MotionKitの設定を条件分岐させて、ユーザーがモーション削減を希望している場合は距離を短くしたりトランジションを無効にするなど、柔軟な配慮を行えます。

import {useEffect, useState} from 'react';
import {MotionCard} from '@jekyll-studio/motionkit';

const useReducedMotion = () => {
  const [reduced, setReduced] = useState(false);

  useEffect(() => {
    const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
    setReduced(mq.matches);
    const handler = (e: MediaQueryListEvent) => setReduced(e.matches);
    mq.addEventListener('change', handler);
    return () => mq.removeEventListener('change', handler);
  }, []);

  return reduced;
};

export const AccessiblePanel = () => {
  const reduced = useReducedMotion();

  return (
    <MotionCard
      motion={
        reduced
          ? {transition: {duration: 0}}
          : {initial: {opacity: 0}, animate: {opacity: 1}}
      }
      style={{padding: 16}}
    >
      <p>ユーザー設定に応じてモーションを自動調整するパネル。</p>
    </MotionCard>
  );
};

第14章 パフォーマンスとベストプラクティス

アニメーションを多用するときは、再レンダリング回数やDOMノード数に注意し、必要に応じてReact.memoや分割レンダリングで負荷を軽減することが大切です。
MotionKitのモーション設定でも、transformやopacityを中心に変更し、レイアウト変更を伴うプロパティのアニメーションを避けると、滑らかな60fpsに近づけられます。

import React from 'react';
import {MotionCard} from '@jekyll-studio/motionkit';

type ItemProps = {label: string};

const AnimatedItem: React.FC<ItemProps> = React.memo(({label}) => (
  <MotionCard
    motion={{whileHover: {scale: 1.03}}}
    style={{padding: 12, marginBottom: 8}}
  >
    {label}
  </MotionCard>
));

export const PerformanceList = () => {
  const items = Array.from({length: 50}, (_, i) => `アイテム ${i + 1}`);
  return (
    <div>
      {items.map((label) => (
        <AnimatedItem key={label} label={label} />
      ))}
    </div>
  );
};

第15章 プロジェクトへの統合と今後の発展

既存のReactプロジェクトにMotionKitを導入する場合は、まずボタンやカードなど頻出コンポーネントを置き換え、徐々にページ全体へモーションポリシーを広げていくとスムーズです。
将来的には、デザインツールのトークンやアニメーション仕様書と連携させて「モーションも含めたデザインシステム」として育てることで、チーム全体の開発体験とユーザー体験を同時に高められます。

// App.tsx の例: プロジェクト全体でMotionKitを活用
import {BrowserRouter, Routes, Route} from 'react-router-dom';
import {AnimatedRouteContainer} from './AnimatedRouteContainer';
import {HomePage} from './pages/Home';
import {SettingsPage} from './pages/Settings';

export const App = () => {
  return (
    <BrowserRouter>
      <AnimatedRouteContainer>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/settings" element={<SettingsPage />} />
        </Routes>
      </AnimatedRouteContainer>
    </BrowserRouter>
  );
};
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?