Reactでアニメーションを実装|CSS Transitionの基本
ReactでCSS Transitionを使ったアニメーション実装方法を詳しく解説。フェードイン・フェードアウト、スライドアニメーション、ホバー効果の作り方から、パフォーマンスの最適化まで初心者向けに説明します。
みなさん、Reactでアニメーションを作りたいと思ったことはありませんか?
「ボタンをクリックしたら、ふわっと要素が現れるようにしたい」 「ページが切り替わる時に、スムーズなアニメーションを付けたい」
こんな風に思ったことありませんか? でも、アニメーションって難しそうで、なかなか手を出せずにいるかもしれませんね。
大丈夫です! 実は、CSS Transitionを使えば、思っているより簡単にアニメーションが作れるんです。
この記事では、Reactで使えるCSS Transitionの基本から実践的な使い方まで、初心者の方にもわかりやすく解説します。 最後まで読めば、あなたもスムーズで美しいアニメーションを作れるようになりますよ!
CSS Transitionって何?
CSS Transitionは、CSSのプロパティが変化する時に、その変化を滑らかにアニメーション化してくれる機能です。
簡単に言うと、「急にパッと変わる」のではなく、「ゆっくりと変化する」ようにしてくれるものなんです。
基本的な仕組みを理解しよう
CSS Transitionは、こんな4つの要素で構成されています。
.element {
/* どのプロパティをアニメーション化するか */
transition-property: opacity, transform;
/* アニメーションの時間 */
transition-duration: 0.3s;
/* アニメーションの動き方 */
transition-timing-function: ease-in-out;
/* 遅延時間 */
transition-delay: 0s;
/* まとめて書くこともできます */
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
}
見てください!
上記のコードでは、opacity
(透明度)とtransform
(変形)というプロパティを、0.3秒かけて滑らかに変化させるという設定をしています。
ease-in-out
は、最初はゆっくり、途中で速くなって、最後にまたゆっくりになる動きです。
自然な感じの動きになるので、よく使われます。
Reactでの基本的な使い方
実際にReactで使ってみましょう!
import { useState } from 'react';
import './FadeAnimation.css';
function FadeAnimation() {
const [isVisible, setIsVisible] = useState(false);
return (
<div>
<button onClick={() => setIsVisible(!isVisible)}>
{isVisible ? '非表示' : '表示'}
</button>
<div
className={`fade-element ${isVisible ? 'visible' : 'hidden'}`}
>
アニメーション対象の要素
</div>
</div>
);
}
JavaScriptの部分では、useState
を使ってisVisible
という状態を管理しています。
ボタンをクリックすると、この状態がtrue
とfalse
で切り替わります。
そして、その状態に応じて要素にvisible
かhidden
のクラスが付きます。
次に、CSSの部分を見てみましょう。
/* FadeAnimation.css */
.fade-element {
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.fade-element.visible {
opacity: 1;
}
.fade-element.hidden {
opacity: 0;
}
最初の.fade-element
で、基本的な透明度を0(完全に透明)に設定しています。
そして、transition
で透明度の変化を0.3秒かけて行うように指定しています。
.visible
クラスが付いた時は透明度が1(完全に不透明)になり、.hidden
クラスが付いた時は透明度が0になります。
この設定により、ボタンをクリックすると要素がふわっと現れたり消えたりするアニメーションが完成します!
フェードイン・フェードアウトを作ってみよう
基本がわかったところで、もう少し実践的なフェードアニメーションを作ってみましょう。
リストアイテムのフェードアニメーション
今度は、リストに項目を追加・削除する時のアニメーションを作ってみます。
function FadeInOut() {
const [items, setItems] = useState([]);
const addItem = () => {
const newItem = {
id: Date.now(),
text: `アイテム ${items.length + 1}`,
isVisible: false
};
setItems(prev => [...prev, newItem]);
// 少し遅延してからアニメーション開始
setTimeout(() => {
setItems(prev =>
prev.map(item =>
item.id === newItem.id
? { ...item, isVisible: true }
: item
)
);
}, 100);
};
const removeItem = (id) => {
// まずアニメーションを開始
setItems(prev =>
prev.map(item =>
item.id === id
? { ...item, isVisible: false }
: item
)
);
// アニメーション完了後に要素を削除
setTimeout(() => {
setItems(prev => prev.filter(item => item.id !== id));
}, 300);
};
return (
<div>
<button onClick={addItem}>アイテム追加</button>
<div className="item-list">
{items.map(item => (
<div
key={item.id}
className={`list-item ${item.isVisible ? 'fade-in' : 'fade-out'}`}
>
{item.text}
<button onClick={() => removeItem(item.id)}>削除</button>
</div>
))}
</div>
</div>
);
}
ちょっと複雑に見えますが、順番に説明しますね。
まず、addItem
関数では新しいアイテムを作成します。
最初はisVisible: false
で、見えない状態にしています。
その後、setTimeout
を使って100ミリ秒後にisVisible: true
に変更します。
これにより、アイテムが追加された瞬間にふわっと現れるアニメーションが実現できます。
removeItem
関数では、まずisVisible: false
に設定してフェードアウトを開始します。
そして、300ミリ秒後(アニメーション完了後)に実際にアイテムを削除しています。
対応するCSSはこんな感じです。
.list-item {
opacity: 0;
transform: translateY(-20px);
transition: opacity 0.3s ease-out, transform 0.3s ease-out;
margin: 10px 0;
padding: 10px;
background: #f5f5f5;
border-radius: 4px;
}
.list-item.fade-in {
opacity: 1;
transform: translateY(0);
}
.list-item.fade-out {
opacity: 0;
transform: translateY(-20px);
}
ここでは、透明度だけでなくtransform
も使っています。
translateY(-20px)
は要素を上に20px移動させるという意味です。
つまり、アイテムが現れる時は「上から下に移動しながらフェードイン」し、消える時は「下から上に移動しながらフェードアウト」するアニメーションになります。
実際に動かしてみると、とても自然で美しいアニメーションが完成しますよ!
スライドアニメーションを実装しよう
次は、要素が横や縦にスライドするアニメーションを作ってみましょう。
横スライドアニメーション
よくあるスライダーのような動きを作ってみます。
function SlideAnimation() {
const [currentIndex, setCurrentIndex] = useState(0);
const slides = [
{ id: 1, title: 'スライド1', content: '最初のスライド' },
{ id: 2, title: 'スライド2', content: '2番目のスライド' },
{ id: 3, title: 'スライド3', content: '3番目のスライド' }
];
const nextSlide = () => {
setCurrentIndex((prev) => (prev + 1) % slides.length);
};
const prevSlide = () => {
setCurrentIndex((prev) => (prev - 1 + slides.length) % slides.length);
};
return (
<div className="slider-container">
<div className="slider-wrapper">
<div
className="slider-content"
style={{
transform: `translateX(-${currentIndex * 100}%)`,
transition: 'transform 0.5s ease-in-out'
}}
>
{slides.map(slide => (
<div key={slide.id} className="slide">
<h2>{slide.title}</h2>
<p>{slide.content}</p>
</div>
))}
</div>
</div>
<div className="slider-controls">
<button onClick={prevSlide}>前へ</button>
<button onClick={nextSlide}>次へ</button>
</div>
</div>
);
}
このコードの核心は、**transform: translateX(-${currentIndex * 100}%)
**の部分です。
currentIndex
が0の時はtranslateX(0%)
で最初の位置、1の時はtranslateX(-100%)
で左に一画面分移動、という具合に動きます。
スライドが3つあるので、インデックスが0、1、2と変わるたびに、画面全体が左右にスライドして見えるんです。
対応するCSSも見てみましょう。
.slider-container {
width: 100%;
max-width: 600px;
margin: 0 auto;
}
.slider-wrapper {
overflow: hidden;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.slider-content {
display: flex;
width: 300%; /* スライド数 × 100% */
}
.slide {
flex: 0 0 100%;
padding: 40px;
text-align: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.slider-controls {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 20px;
}
.slider-controls button {
padding: 10px 20px;
border: none;
border-radius: 4px;
background: #667eea;
color: white;
cursor: pointer;
transition: background 0.2s ease;
}
.slider-controls button:hover {
background: #5a6fd8;
}
ポイントは、.slider-content
のwidth: 300%
です。
スライドが3つあるので、全体の幅を300%にして、3つのスライドを横に並べています。
.slider-wrapper
でoverflow: hidden
を設定しているので、現在見えているスライド以外は隠れます。
縦スライドアニメーション
今度は、縦方向のスライドアニメーションを作ってみましょう。 よくあるアコーディオンのような動きです。
function VerticalSlide() {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div className="expandable-card">
<div className="card-header" onClick={() => setIsExpanded(!isExpanded)}>
<h3>詳細情報</h3>
<span className={`arrow ${isExpanded ? 'expanded' : ''}`}>▼</span>
</div>
<div className={`card-content ${isExpanded ? 'expanded' : 'collapsed'}`}>
<p>これは展開可能なコンテンツです。</p>
<p>クリックすることで表示・非表示が切り替わります。</p>
<p>スムーズなアニメーションで展開されます。</p>
</div>
</div>
);
}
このコードでは、ヘッダーをクリックするとisExpanded
の状態が切り替わります。
その状態に応じて、コンテンツ部分が展開されたり折り畳まれたりします。
対応するCSSを見てみましょう。
.expandable-card {
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
margin: 20px 0;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: #f8f9fa;
cursor: pointer;
transition: background 0.2s ease;
}
.card-header:hover {
background: #e9ecef;
}
.arrow {
transition: transform 0.3s ease;
}
.arrow.expanded {
transform: rotate(180deg);
}
.card-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out, padding 0.3s ease-out;
}
.card-content.expanded {
max-height: 200px;
padding: 15px;
}
.card-content.collapsed {
max-height: 0;
padding: 0 15px;
}
ここでのポイントは、max-height
を使ってアニメーションを実現していることです。
通常、height: auto
からheight: 0
のようなアニメーションは上手く動きません。
でも、max-height
を使うことで、自然な高さの変化をアニメーション化できるんです。
.arrow
の回転アニメーションも素敵ですよね。
transform: rotate(180deg)
で矢印が180度回転して、開閉の状態を視覚的に表現しています。
ホバー効果でユーザー体験を向上させよう
マウスを要素の上に乗せた時(ホバー時)のアニメーションも、ユーザー体験を向上させる重要な要素です。
インタラクティブなボタンを作ろう
まずは、ホバー時に動きのあるボタンを作ってみましょう。
function AnimatedButton({ children, onClick }) {
const [isHovered, setIsHovered] = useState(false);
return (
<button
className={`animated-button ${isHovered ? 'hovered' : ''}`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
onClick={onClick}
>
<span className="button-content">{children}</span>
<span className="button-background"></span>
</button>
);
}
function ButtonDemo() {
return (
<div>
<AnimatedButton onClick={() => alert('クリックされました')}>
アニメーションボタン
</AnimatedButton>
</div>
);
}
このボタンは、マウスが乗った時にonMouseEnter
が発火し、離れた時にonMouseLeave
が発火します。
その状態に応じて、isHovered
の値が変わり、ボタンの見た目が変化します。
対応するCSSを見てみましょう。
.animated-button {
position: relative;
padding: 12px 24px;
border: none;
border-radius: 6px;
background: #007bff;
color: white;
cursor: pointer;
overflow: hidden;
transition: all 0.3s ease;
}
.button-content {
position: relative;
z-index: 2;
transition: transform 0.3s ease;
}
.button-background {
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s ease;
}
.animated-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
}
.animated-button.hovered .button-background {
left: 100%;
}
このCSSでは、複数のアニメーション効果を組み合わせています。
まず、:hover
の時にtranslateY(-2px)
でボタンが少し上に浮き上がるような効果を付けています。
同時にbox-shadow
で影を付けて、より立体的に見えるようにしています。
.button-background
は、ホバー時にボタンの上を光が流れるような効果を作っています。
最初はleft: -100%
で画面外の左側にいて、ホバー時にleft: 100%
で右側に移動します。
美しいカードアニメーション
次は、カードにホバー効果を付けてみましょう。
function AnimatedCard({ title, description, image }) {
return (
<div className="card-container">
<div className="card-image">
<img src={image} alt={title} />
<div className="card-overlay">
<span>詳細を見る</span>
</div>
</div>
<div className="card-body">
<h3>{title}</h3>
<p>{description}</p>
</div>
</div>
);
}
function CardGallery() {
const cards = [
{
title: 'カード1',
description: '説明文1',
image: '/images/card1.jpg'
},
{
title: 'カード2',
description: '説明文2',
image: '/images/card2.jpg'
}
];
return (
<div className="card-gallery">
{cards.map((card, index) => (
<AnimatedCard key={index} {...card} />
))}
</div>
);
}
このカードは、シンプルなReactコンポーネントですが、CSSで素敵なアニメーション効果を付けています。
.card-container {
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
background: white;
}
.card-container:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
}
.card-image {
position: relative;
overflow: hidden;
}
.card-image img {
width: 100%;
height: 200px;
object-fit: cover;
transition: transform 0.3s ease;
}
.card-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
color: white;
opacity: 0;
transition: opacity 0.3s ease;
}
.card-container:hover .card-image img {
transform: scale(1.1);
}
.card-container:hover .card-overlay {
opacity: 1;
}
.card-body {
padding: 20px;
}
.card-gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin: 20px 0;
}
このカードでは、ホバー時に3つのアニメーション効果を組み合わせています。
- カード全体が上に浮き上がる(
translateY(-5px)
) - 画像が少し拡大される(
scale(1.1)
) - オーバーレイが表示される(
opacity: 0
からopacity: 1
)
これらの効果が組み合わさることで、とても洗練された印象のカードアニメーションが完成します。
パフォーマンスを最適化しよう
アニメーションを作る時は、パフォーマンスのことも考える必要があります。
GPU加速を活用しよう
ブラウザには「GPU加速」という機能があります。 これを使うと、アニメーションがより滑らかに動作します。
.optimized-animation {
/* transform と opacity のみを使用 */
transform: translateZ(0); /* GPU加速を強制 */
will-change: transform, opacity; /* ブラウザに最適化のヒントを提供 */
}
.slide-element {
/* レイアウトを変更しないプロパティを使用 */
transform: translateX(0);
transition: transform 0.3s ease;
}
.slide-element.moved {
transform: translateX(100px);
}
ここでのポイントは、transform
とopacity
のみを使用していることです。
これらのプロパティは、ブラウザのレンダリングエンジンが最適化しやすく、滑らかなアニメーションを実現できます。
逆に、width
やheight
、margin
などのプロパティをアニメーション化すると、ブラウザがレイアウトを再計算する必要があり、重い処理になってしまいます。
不要な再レンダリングを防ごう
Reactでは、不要な再レンダリングもパフォーマンスに影響します。
// React.memo を使用してパフォーマンスを最適化
const AnimatedItem = React.memo(({ item, isVisible }) => {
return (
<div className={`item ${isVisible ? 'visible' : 'hidden'}`}>
{item.content}
</div>
);
});
function OptimizedList() {
const [items, setItems] = useState([]);
const [visibleItems, setVisibleItems] = useState(new Set());
// アニメーションの状態のみを更新
const toggleVisibility = (id) => {
setVisibleItems(prev => {
const newSet = new Set(prev);
if (newSet.has(id)) {
newSet.delete(id);
} else {
newSet.add(id);
}
return newSet;
});
};
return (
<div>
{items.map(item => (
<AnimatedItem
key={item.id}
item={item}
isVisible={visibleItems.has(item.id)}
/>
))}
</div>
);
}
React.memo
を使うことで、propsが変わった時だけ再レンダリングされるようになります。
また、Set
を使って表示状態を管理することで、効率的な状態更新を実現しています。
実践的なアニメーションパターン
実際の開発でよく使われるアニメーションパターンを見てみましょう。
ローディングアニメーション
データを取得中などに表示するローディングアニメーションです。
function LoadingSpinner() {
return (
<div className="loading-container">
<div className="spinner"></div>
<p>読み込み中...</p>
</div>
);
}
function DataLoader() {
const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
useEffect(() => {
fetchData()
.then(result => {
setData(result);
setLoading(false);
})
.catch(error => {
console.error('Error:', error);
setLoading(false);
});
}, []);
if (loading) {
return <LoadingSpinner />;
}
return (
<div className="data-container fade-in">
{data && (
<div>
<h2>データが読み込まれました</h2>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)}
</div>
);
}
このコードでは、データの取得中はLoadingSpinner
を表示し、取得完了後はデータを表示します。
対応するCSSはこんな感じです。
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 40px;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.data-container {
opacity: 0;
transition: opacity 0.5s ease-in;
}
.data-container.fade-in {
opacity: 1;
}
スピナーは、@keyframes
を使って無限に回転するアニメーションを作っています。
データが取得されると、.fade-in
クラスが付いて、ふわっと表示されます。
通知メッセージのアニメーション
ユーザーにメッセージを表示する通知システムです。
function NotificationSystem() {
const [notifications, setNotifications] = useState([]);
const addNotification = (message, type = 'info') => {
const id = Date.now();
const notification = { id, message, type, visible: false };
setNotifications(prev => [...prev, notification]);
// アニメーション開始
setTimeout(() => {
setNotifications(prev =>
prev.map(n => n.id === id ? { ...n, visible: true } : n)
);
}, 100);
// 自動削除
setTimeout(() => {
removeNotification(id);
}, 3000);
};
const removeNotification = (id) => {
setNotifications(prev =>
prev.map(n => n.id === id ? { ...n, visible: false } : n)
);
setTimeout(() => {
setNotifications(prev => prev.filter(n => n.id !== id));
}, 300);
};
return (
<div>
<div className="notification-buttons">
<button onClick={() => addNotification('成功メッセージ', 'success')}>
成功通知
</button>
<button onClick={() => addNotification('エラーメッセージ', 'error')}>
エラー通知
</button>
</div>
<div className="notification-container">
{notifications.map(notification => (
<div
key={notification.id}
className={`notification ${notification.type} ${
notification.visible ? 'slide-in' : 'slide-out'
}`}
>
{notification.message}
<button onClick={() => removeNotification(notification.id)}>
×
</button>
</div>
))}
</div>
</div>
);
}
この通知システムでは、以下のような流れでアニメーションが動きます。
- 通知が作成されると、まず見えない状態で配列に追加
- 100ミリ秒後に
visible: true
にしてスライドイン - 3秒後に
visible: false
にしてスライドアウト - 300ミリ秒後に配列から削除
対応するCSSです。
.notification-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
}
.notification {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
margin-bottom: 10px;
border-radius: 6px;
color: white;
min-width: 300px;
transform: translateX(100%);
opacity: 0;
transition: all 0.3s ease;
}
.notification.success {
background: #28a745;
}
.notification.error {
background: #dc3545;
}
.notification.info {
background: #17a2b8;
}
.notification.slide-in {
transform: translateX(0);
opacity: 1;
}
.notification.slide-out {
transform: translateX(100%);
opacity: 0;
}
.notification button {
background: none;
border: none;
color: white;
cursor: pointer;
font-size: 18px;
padding: 0;
margin-left: 10px;
}
通知は画面の右上から右にスライドインし、消える時は右にスライドアウトします。
position: fixed
を使って、画面の固定位置に表示されます。
まとめ
お疲れ様でした! ReactでCSS Transitionを使ったアニメーションの実装方法を、たくさん学びましたね。
今回学んだ重要なポイントをまとめておきましょう。
CSS Transitionの基本
transition
プロパティで滑らかなアニメーションを実現transform
とopacity
を使うとパフォーマンスが良い- タイミング関数で自然な動きを演出
実装のコツ
- 状態管理とCSSクラスの組み合わせが重要
setTimeout
を使ってアニメーションのタイミングを調整React.memo
で不要な再レンダリングを防ぐ
実践的なパターン
- フェードイン・アウト、スライド、ホバー効果
- ローディングアニメーション、通知システム
- 複数のアニメーション効果の組み合わせ
アニメーションは、ユーザー体験を大きく向上させる重要な要素です。 最初は簡単なフェードアニメーションから始めて、徐々に複雑なものにチャレンジしてみてください。
何より大切なのは、実際に手を動かして試してみることです。 コードをコピーして、自分なりにアレンジしてみてくださいね!
きっと、あなたのアプリケーションがより魅力的になりますよ。 ぜひ、今日学んだ技術を実際のプロジェクトで活用してみてください!