Motion(旧 Framer Motion)Gesture アニメーション実践:whileHover/whileTap/whileFocus の設計術

Motion(旧 Framer Motion)Gesture アニメーション実践:whileHover/whileTap/whileFocus の設計術
モダンな Web アプリケーションにおいて、ユーザーとの自然なインタラクションを実現するためには、単なる機能提供だけでなく、直感的で心地よい操作体験が不可欠です。
そこで注目したいのが、Motion(旧 Framer Motion)のジェスチャーアニメーション機能です。whileHover、whileTap、whileFocus という 3 つの強力なプロップスを使いこなすことで、従来の CSS では実現困難だった滑らかで響きのあるユーザー体験を構築できます。
背景
Motion のジェスチャーアニメーション概要
Motion(2024 年に Framer Motion から改名)は、React アプリケーション用のアニメーションライブラリとして、宣言的なアプローチでリッチなインタラクションを実装できる優れたツールです。
特にジェスチャーアニメーションにおいては、ユーザーの操作に対する即座のフィードバックを提供することで、アプリケーションに生命力を吹き込みます。
typescriptimport { motion } from 'motion/react';
// 基本的なジェスチャー対応コンポーネント
const InteractiveButton = () => {
return (
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
whileFocus={{ boxShadow: '0 0 0 3px #3b82f6' }}
>
クリックしてください
</motion.button>
);
};
whileHover、whileTap、whileFocus の基本概念
これら 3 つのプロップスは、それぞれ異なるユーザー操作に反応してアニメーションを実行します。
ジェスチャー | 発動条件 | 主な用途 |
---|---|---|
whileHover | マウスホバー | 要素への注目喚起 |
whileTap | タップ・クリック | 操作フィードバック |
whileFocus | フォーカス取得 | アクセシビリティ対応 |
以下の図は、各ジェスチャーがユーザー操作とどのように連動するかを示しています。
mermaidflowchart LR
user[ユーザー] -->|マウスオーバー| hover[whileHover]
user -->|クリック/タップ| tap[whileTap]
user -->|キーボード操作| focus[whileFocus]
hover -->|スケール変更| visual1[視覚フィードバック]
tap -->|色彩変更| visual2[操作フィードバック]
focus -->|境界線表示| visual3[フォーカス表示]
図で理解できる要点:
- 各操作は独立したアニメーショントリガーとなる
- 視覚的フィードバックがユーザー体験を向上させる
- アクセシビリティにも配慮した設計が可能
インタラクティブ UI における役割
現代の Web アプリケーションでは、静的なインターフェースから動的で応答性の高いインタラクションへとパラダイムシフトが起きています。
Motion のジェスチャーアニメーションは、この変化において中核的な役割を担い、ユーザーの操作意図と画面上の反応を自然に結びつけます。
課題
従来の CSS Hover との違いと制約
従来の CSS :hover
擬似クラスには、いくつかの制約がありました。
css/* 従来のCSS Hover */
.button:hover {
transform: scale(1.1);
transition: transform 0.3s ease;
}
この実装では、以下のような問題が発生することがあります。
従来の CSS Hover の制約
- アニメーション制御の柔軟性が限定的
- 複雑な状態遷移の実装が困難
- JavaScript との連携が複雑
- モバイルデバイスでの一貫性のない動作
一方、Motion の whileHover は、これらの課題を解決します。
typescript// Motion による改善されたホバー実装
<motion.div
whileHover={{
scale: 1.1,
transition: { duration: 0.2, ease: 'easeOut' },
}}
>
改善されたホバー体験
</motion.div>
パフォーマンスとアクセシビリティの課題
ジェスチャーアニメーションを実装する際、パフォーマンスとアクセシビリティのバランスを取ることが重要な課題となります。
パフォーマンス課題
- 大量の要素に適用した場合のメモリ使用量
- 60FPS を維持するための最適化
- モバイルデバイスでのバッテリー消費
アクセシビリティ課題
- 運動機能に制限のあるユーザーへの配慮
- スクリーンリーダーとの互換性
- reduce-motion メディアクエリへの対応
複数のジェスチャーを組み合わせる際の問題点
単一のジェスチャーは実装が簡単ですが、複数を組み合わせる際には以下の課題が生じます。
mermaidstateDiagram-v2
state "デフォルト" as BASE
[*] --> BASE
BASE --> hovered : マウスオーバー
BASE --> focused : キーボードフォーカス
hovered --> tapped : クリック
focused --> tapped : Enter/Space
tapped --> BASE : 操作完了
note right of tapped : 複数状態の競合が発生する可能性
図で理解できる要点:
- 状態遷移が複雑になりがち
- 複数のジェスチャーが同時発生する可能性
- アニメーション競合の制御が必要
解決策
Motion のジェスチャープロップス設計思想
Motion は、宣言的なアプローチでジェスチャーアニメーションを実装できるよう設計されています。
この設計思想の核心は、「状態」と「アニメーション」を分離し、React の理念に沿った直感的な記述を可能にすることです。
typescript// Motion の宣言的アプローチ
const Component = () => {
return (
<motion.div
// 各状態に対応したアニメーションを宣言的に定義
animate={{ opacity: 1 }}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.98 }}
whileFocus={{
outline: '2px solid #3b82f6',
outlineOffset: '2px',
}}
>
宣言的ジェスチャー対応要素
</motion.div>
);
};
各ジェスチャーの効果的な活用方法
効果的なジェスチャーアニメーションを実装するためには、各ジェスチャーの特性を理解し、適切な場面で使い分けることが重要です。
whileHover の効果的な活用
- 要素の発見可能性向上
- 操作可能であることの示唆
- ブランドイメージの強化
whileTap の効果的な活用
- 即座の操作フィードバック
- ユーザーの操作確信提供
- タッチデバイスでの触覚的フィードバック代替
whileFocus の効果的な活用
- キーボードユーザーへの配慮
- アクセシビリティ基準への準拠
- 操作フローの視覚的ガイド
アニメーション設計のベストプラクティス
成功するジェスチャーアニメーションには、以下のベストプラクティスを適用します。
1. 適度な動きの設計
typescript// 過度でない、自然な動きを心がける
const subtleAnimation = {
scale: 1.02, // 大きすぎない拡大
transition: {
duration: 0.15, // 短めの持続時間
ease: 'easeOut', // 自然なイージング
},
};
2. 一貫性のあるタイミング
アプリケーション全体で統一されたアニメーションタイミングを使用することで、ユーザー体験の統一性を保ちます。
具体例
whileHover の実装とカスタマイズ
基本的なホバーアニメーション
最もシンプルなホバーアニメーションから始めましょう。
typescriptimport { motion } from 'motion/react';
const BasicHoverButton = () => {
return (
<motion.button
className='px-6 py-3 bg-blue-500 text-white rounded-lg'
whileHover={{
scale: 1.05,
backgroundColor: '#3b82f6',
}}
transition={{ duration: 0.2 }}
>
基本ホバーボタン
</motion.button>
);
};
このサンプルでは、ホバー時にボタンが軽くスケールアップし、背景色が変化します。
複雑なホバーエフェクトの実装
より洗練されたホバーエフェクトを実装してみましょう。
typescriptconst AdvancedHoverCard = () => {
return (
<motion.div
className='p-6 bg-white rounded-xl shadow-lg cursor-pointer'
whileHover={{
y: -8,
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
transition: { duration: 0.3, ease: 'easeOut' },
}}
>
<motion.div
whileHover={{ scale: 1.02 }}
transition={{ duration: 0.2 }}
>
<h3 className='text-xl font-bold mb-2'>
高度なホバーカード
</h3>
<p className='text-gray-600'>
複数のアニメーションを組み合わせた洗練された効果
</p>
</motion.div>
</motion.div>
);
};
レスポンシブ対応とタッチデバイス配慮
タッチデバイスでは、ホバー状態が存在しないため、適切な配慮が必要です。
typescriptimport { useMediaQuery } from 'react-responsive';
const ResponsiveHoverElement = () => {
const isTouch = useMediaQuery({
query: '(pointer: coarse)',
});
return (
<motion.div
whileHover={
!isTouch
? {
scale: 1.05,
transition: { duration: 0.2 },
}
: {}
}
whileTap={{
scale: 0.98,
transition: { duration: 0.1 },
}}
className='p-4 bg-gradient-to-r from-purple-500 to-blue-500 text-white rounded-lg'
>
レスポンシブ対応要素
</motion.div>
);
};
whileTap の実装とカスタマイズ
タップアニメーションの基本実装
タップアニメーションは、ユーザーの操作に対する即座のフィードバックを提供します。
typescriptconst TapFeedbackButton = () => {
return (
<motion.button
className='px-8 py-4 bg-green-500 text-white font-semibold rounded-full'
whileTap={{
scale: 0.95,
backgroundColor: '#10b981',
}}
transition={{
type: 'spring',
stiffness: 400,
damping: 17,
}}
>
タップフィードバックボタン
</motion.button>
);
};
Spring アニメーションを使用することで、より自然な弾力感のあるフィードバックを実現できます。
フィードバック効果の最適化
効果的なタップフィードバックには、視覚的要素と時間的要素の両方を最適化する必要があります。
typescriptconst OptimizedTapButton = () => {
return (
<motion.button
className='relative px-6 py-3 bg-indigo-600 text-white rounded-lg overflow-hidden'
whileTap={{
scale: 0.98,
transition: { duration: 0.1, ease: 'easeInOut' },
}}
>
{/* リップルエフェクトの実装 */}
<motion.div
className='absolute inset-0 bg-white'
initial={{ scale: 0, opacity: 0.5 }}
whileTap={{
scale: 1,
opacity: 0,
transition: { duration: 0.4, ease: 'easeOut' },
}}
style={{ borderRadius: '50%' }}
/>
最適化されたタップボタン
</motion.button>
);
};
タップとクリックの使い分け
デスクトップとモバイルの両方で一貫した体験を提供するための実装です。
typescriptconst UniversalInteractiveElement = () => {
const handleInteraction = () => {
console.log('ユーザーインタラクション発生');
};
return (
<motion.div
className='p-4 bg-orange-500 text-white rounded-lg cursor-pointer select-none'
whileTap={{
scale: 0.97,
rotate: -1,
}}
onTap={handleInteraction}
onClick={handleInteraction} // フォールバック
>
ユニバーサルインタラクション要素
</motion.div>
);
};
whileFocus の実装とカスタマイズ
フォーカス状態のアニメーション
キーボードナビゲーションユーザーのためのフォーカス表示を実装します。
typescriptconst FocusableInput = () => {
return (
<motion.input
type='text'
placeholder='フォーカス対応入力フィールド'
className='px-4 py-2 border border-gray-300 rounded-lg'
whileFocus={{
scale: 1.02,
borderColor: '#3b82f6',
boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.1)',
transition: { duration: 0.2 },
}}
/>
);
};
キーボードナビゲーション対応
Tab キーを使った操作フローでの視認性を向上させます。
typescriptconst NavigationMenu = () => {
const menuItems = [
'ホーム',
'製品',
'会社概要',
'お問い合わせ',
];
return (
<nav className='flex space-x-4'>
{menuItems.map((item, index) => (
<motion.a
key={index}
href={`#${item}`}
className='px-3 py-2 text-gray-700 rounded-md focus:outline-none'
whileFocus={{
backgroundColor: '#f3f4f6',
scale: 1.05,
transition: { duration: 0.15 },
}}
tabIndex={0}
>
{item}
</motion.a>
))}
</nav>
);
};
アクセシビリティ考慮事項
prefers-reduced-motion
メディアクエリに対応した実装例です。
typescriptimport { useReducedMotion } from 'motion/react';
const AccessibleFocusElement = () => {
const shouldReduceMotion = useReducedMotion();
return (
<motion.button
className='px-6 py-3 bg-purple-600 text-white rounded-lg'
whileFocus={
shouldReduceMotion
? {
// モーションを減らした控えめなアニメーション
outline: '2px solid #8b5cf6',
outlineOffset: '2px',
}
: {
// 通常のアニメーション
scale: 1.03,
boxShadow:
'0 0 0 4px rgba(139, 92, 246, 0.3)',
transition: { duration: 0.2 },
}
}
>
アクセシブルなフォーカス要素
</motion.button>
);
};
複合ジェスチャーの実装
複数ジェスチャーの組み合わせ技法
すべてのジェスチャーを統合した包括的なインタラクティブコンポーネントを作成します。
typescriptconst ComprehensiveInteractiveCard = () => {
const [isSelected, setIsSelected] = useState(false);
return (
<motion.div
className={`p-6 rounded-xl cursor-pointer ${
isSelected
? 'bg-blue-100 border-blue-300'
: 'bg-white border-gray-200'
} border-2`}
// ホバー時のアニメーション
whileHover={{
y: -4,
boxShadow: '0 10px 25px -3px rgba(0, 0, 0, 0.1)',
borderColor: '#3b82f6',
}}
// タップ時のアニメーション
whileTap={{
scale: 0.98,
y: -2,
}}
// フォーカス時のアニメーション
whileFocus={{
outline: '2px solid #3b82f6',
outlineOffset: '2px',
}}
onClick={() => setIsSelected(!isSelected)}
tabIndex={0}
>
<h3 className='text-xl font-bold mb-2'>
統合インタラクティブカード
</h3>
<p className='text-gray-600'>
ホバー、タップ、フォーカスすべてに対応したカードコンポーネント
</p>
</motion.div>
);
};
状態管理とアニメーション制御
複雑な状態を持つコンポーネントでのアニメーション制御を実装します。
typescriptconst StatefulInteractiveButton = () => {
const [state, setState] = useState<
'idle' | 'loading' | 'success'
>('idle');
const variants = {
idle: {
scale: 1,
backgroundColor: '#3b82f6',
},
loading: {
scale: 1.05,
backgroundColor: '#6366f1',
transition: {
scale: {
repeat: Infinity,
repeatType: 'reverse',
duration: 0.5,
},
},
},
success: {
scale: 1,
backgroundColor: '#10b981',
},
};
return (
<motion.button
className='px-6 py-3 text-white rounded-lg font-medium'
variants={variants}
animate={state}
whileHover={state === 'idle' ? { scale: 1.05 } : {}}
whileTap={state === 'idle' ? { scale: 0.95 } : {}}
onClick={() => {
if (state === 'idle') {
setState('loading');
// 2秒後に成功状態に遷移
setTimeout(() => setState('success'), 2000);
setTimeout(() => setState('idle'), 3500);
}
}}
>
{state === 'idle' && '開始'}
{state === 'loading' && '処理中...'}
{state === 'success' && '完了!'}
</motion.button>
);
};
パフォーマンス最適化
大量の要素や複雑なアニメーションでのパフォーマンス最適化技法です。
typescriptimport { motion, useAnimation } from 'motion/react';
import { useInView } from 'react-intersection-observer';
const OptimizedAnimatedList = ({
items,
}: {
items: string[];
}) => {
return (
<div className='space-y-2'>
{items.map((item, index) => (
<OptimizedListItem key={index} item={item} />
))}
</div>
);
};
const OptimizedListItem = ({ item }: { item: string }) => {
const controls = useAnimation();
const [ref, inView] = useInView({
threshold: 0.1,
triggerOnce: true,
});
// ビューポートに入った時のみアニメーションを有効化
React.useEffect(() => {
if (inView) {
controls.start('visible');
}
}, [controls, inView]);
return (
<motion.div
ref={ref}
initial='hidden'
animate={controls}
variants={{
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
}}
// インビュー時のみジェスチャーアニメーションを有効化
whileHover={inView ? { x: 10 } : {}}
whileTap={inView ? { scale: 0.98 } : {}}
className='p-4 bg-white rounded-lg shadow-sm border cursor-pointer'
>
{item}
</motion.div>
);
};
以下の図は、パフォーマンス最適化の仕組みを示しています。
mermaidflowchart TD
viewport[ビューポート] --> intersection[Intersection Observer]
intersection --> check{要素が見える?}
check -->|Yes| enable[アニメーション有効化]
check -->|No| disable[アニメーション無効化]
enable --> gesture[ジェスチャー検出開始]
disable --> skip[GPU リソース節約]
gesture --> animate[スムーズアニメーション]
図で理解できる要点:
- ビューポート外の要素はアニメーションを無効化
- 必要な時のみ GPU リソースを使用
- パフォーマンスとユーザー体験の両立を実現
まとめ
Motion のジェスチャーアニメーション機能(whileHover、whileTap、whileFocus)は、現代的な Web アプリケーションにおいて不可欠な要素となっています。
これらの機能を効果的に活用することで、以下のメリットが得られます。
実装面でのメリット
- 宣言的で React らしい記述方法
- CSS transitions よりも柔軟な制御
- パフォーマンスの最適化された実装
ユーザー体験面でのメリット
- 直感的で応答性の高いインタラクション
- アクセシビリティへの適切な配慮
- デバイスを問わない一貫した操作感
開発効率面でのメリット
- 複雑な状態管理の簡素化
- 再利用可能なコンポーネント設計
- メンテナブルなアニメーション実装
成功の鍵は、過度に複雑にならず、ユーザーの期待に応える自然なアニメーションを心がけることです。また、アクセシビリティやパフォーマンスへの配慮を忘れずに、すべてのユーザーが快適に利用できるインターフェースを構築していきましょう。
Motion のジェスチャーアニメーションを適切に実装することで、単なる機能提供を超えた、ユーザーに愛され続ける Web アプリケーションの開発が可能になります。
関連リンク
- article
Motion(旧 Framer Motion)Gesture アニメーション実践:whileHover/whileTap/whileFocus の設計術
- article
Motion(旧 Framer Motion)基本 API 徹底解説:motion 要素・initial/animate/exit の正しい使い方
- article
移行ガイド:Framer Motion から Motion への変更点と対応策
- article
Motion(旧 Framer Motion)入門:React アニメーションを最速で始める
- article
2025 年版 Motion(旧 Framer Motion) 徹底活用テクニック集
- article
Prisma の公式ドキュメントを使い倒すためのコツ
- article
GitHub Actions × Node.js:テストとデプロイを自動化する
- article
Pinia × TypeScript:型安全なストア設計入門
- article
Obsidian デイリーノート活用術:毎日の思考ログを資産に変える方法
- article
Git で特定のコミットを打ち消す!git revert の正しい使い方
- article
Gemini CLI のストリーミング出力:逐次生成を活かした UX 改善手法
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来