Emotion の keyframes で美しいアニメーションを作る

Web アプリケーションにおいて、アニメーションは単なる装飾ではありません。ユーザー体験を劇的に向上させ、インタラクションを直感的にする重要な要素です。
Emotion の keyframes を使えば、CSS アニメーションを JavaScript の力で動的に制御でき、型安全性も確保できます。この記事では、Emotion の keyframes を使った美しいアニメーションの作成方法を、実践的な例と共に詳しく解説していきます。
あなたも、この記事を読むことで、ユーザーが「わあ、すごい!」と感動するようなアニメーションを実装できるようになるでしょう。
keyframes の基本概念
keyframes は、アニメーションの開始から終了までの中間状態を定義する仕組みです。CSS の @keyframes
ルールを Emotion で使うことで、より柔軟で動的なアニメーションが可能になります。
従来の CSS keyframes との違い
従来の CSS では、keyframes は静的に定義する必要がありました:
css@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
しかし、Emotion を使うと、動的に keyframes を生成できます:
typescriptconst fadeIn = keyframes`
from { opacity: 0; }
to { opacity: 1; }
`;
この違いにより、テーマやプロパティに応じてアニメーションを動的に調整できるようになります。
Emotion での keyframes 実装方法
Emotion で keyframes を使うには、まず @emotion/react
から keyframes
をインポートする必要があります。
基本的なセットアップ
typescriptimport { keyframes } from '@emotion/react';
import styled from '@emotion/styled';
// keyframes の定義
const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;
// styled-components での使用
const FadeInBox = styled.div`
animation: ${fadeIn} 0.5s ease-in-out;
`;
よくあるエラーと解決方法
エラー 1: keyframes がインポートされていない
typescript// ❌ エラーが発生するコード
const animation = keyframes`
from { opacity: 0; }
to { opacity: 1; }
`;
// ✅ 正しいコード
import { keyframes } from '@emotion/react';
const animation = keyframes`
from { opacity: 0; }
to { opacity: 1; }
`;
エラー 2: テンプレートリテラルの構文エラー
typescript// ❌ エラーが発生するコード
const animation = keyframes(
'from { opacity: 0; } to { opacity: 1; }'
);
// ✅ 正しいコード
const animation = keyframes`
from { opacity: 0; }
to { opacity: 1; }
`;
基本的なアニメーション例
フェードイン・フェードアウト
最も基本的で効果的なアニメーションから始めましょう。
typescriptimport { keyframes } from '@emotion/react';
import styled from '@emotion/styled';
// フェードインアニメーション
const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;
// フェードアウトアニメーション
const fadeOut = keyframes`
from {
opacity: 1;
}
to {
opacity: 0;
}
`;
const FadeInOutBox = styled.div<{ isVisible: boolean }>`
animation: ${(props) =>
props.isVisible ? fadeIn : fadeOut} 0.3s ease-in-out;
opacity: ${(props) => (props.isVisible ? 1 : 0)};
`;
このコンポーネントの使用例:
typescriptconst MyComponent = () => {
const [isVisible, setIsVisible] = useState(true);
return (
<FadeInOutBox isVisible={isVisible}>
アニメーションするコンテンツ
</FadeInOutBox>
);
};
スライドイン・スライドアウト
要素が画面外から滑らかに登場するアニメーションです。
typescript// 左からスライドイン
const slideInLeft = keyframes`
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
`;
// 右からスライドイン
const slideInRight = keyframes`
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
`;
const SlideInBox = styled.div<{
direction: 'left' | 'right';
}>`
animation: ${(props) =>
props.direction === 'left'
? slideInLeft
: slideInRight} 0.5s ease-out;
`;
スケールアニメーション
要素のサイズを変化させるアニメーションです。
typescript// 拡大アニメーション
const scaleIn = keyframes`
from {
transform: scale(0);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
`;
// 縮小アニメーション
const scaleOut = keyframes`
from {
transform: scale(1);
opacity: 1;
}
to {
transform: scale(0);
opacity: 0;
}
`;
const ScaleBox = styled.div<{ isExpanded: boolean }>`
animation: ${(props) =>
props.isExpanded ? scaleIn : scaleOut} 0.3s ease-in-out;
transform-origin: center;
`;
高度なアニメーションテクニック
複数の keyframes を組み合わせる
複数のアニメーションを同時に実行することで、より豊かな表現が可能になります。
typescript// フェードイン + スライドイン
const fadeInSlideUp = keyframes`
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
`;
// 回転 + スケール
const rotateScale = keyframes`
0% {
transform: rotate(0deg) scale(1);
}
50% {
transform: rotate(180deg) scale(1.2);
}
100% {
transform: rotate(360deg) scale(1);
}
`;
const CombinedAnimationBox = styled.div`
animation: ${fadeInSlideUp} 0.6s ease-out, ${rotateScale}
2s ease-in-out infinite;
`;
イージング関数の活用
イージング関数を適切に選ぶことで、アニメーションがより自然で魅力的になります。
typescript// バウンス効果
const bounce = keyframes`
0%, 20%, 53%, 80%, 100% {
transform: translateY(0);
}
40%, 43% {
transform: translateY(-30px);
}
70% {
transform: translateY(-15px);
}
90% {
transform: translateY(-4px);
}
`;
// イージング関数の例
const BounceBox = styled.div`
animation: ${bounce} 1s cubic-bezier(0.68, -0.55, 0.265, 1.55);
`;
const ElasticBox = styled.div`
animation: ${fadeInSlideUp} 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275);
`;
アニメーションの連鎖
複数の要素を順番にアニメーションさせることで、視覚的な流れを作れます。
typescriptconst staggeredFadeIn = keyframes`
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
`;
const StaggeredItem = styled.div<{ delay: number }>`
animation: ${staggeredFadeIn} 0.5s ease-out;
animation-delay: ${(props) => props.delay}s;
animation-fill-mode: both;
`;
// 使用例
const StaggeredList = () => (
<div>
<StaggeredItem delay={0}>アイテム 1</StaggeredItem>
<StaggeredItem delay={0.1}>アイテム 2</StaggeredItem>
<StaggeredItem delay={0.2}>アイテム 3</StaggeredItem>
<StaggeredItem delay={0.3}>アイテム 4</StaggeredItem>
</div>
);
実践的なコンポーネント例
ローディングスピナー
ユーザーに待機時間を伝える美しいスピナーを作成しましょう。
typescriptconst spin = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
const pulse = keyframes`
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
`;
const Spinner = styled.div`
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: ${spin} 1s linear infinite;
`;
const PulseSpinner = styled.div`
width: 40px;
height: 40px;
background-color: #3498db;
border-radius: 50%;
animation: ${pulse} 1.5s ease-in-out infinite;
`;
ホバーエフェクト
ユーザーの操作に反応する魅力的なホバーエフェクトです。
typescriptconst lift = keyframes`
from {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
to {
transform: translateY(-4px);
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
}
`;
const HoverCard = styled.div`
padding: 20px;
background: white;
border-radius: 8px;
transition: all 0.3s ease;
cursor: pointer;
&:hover {
animation: ${lift} 0.3s ease-out forwards;
}
`;
ページ遷移アニメーション
ページ間の移動を滑らかにするアニメーションです。
typescriptconst pageEnter = keyframes`
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
`;
const pageExit = keyframes`
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(-100%);
}
`;
const PageTransition = styled.div<{ isEntering: boolean }>`
animation: ${(props) =>
props.isEntering ? pageEnter : pageExit} 0.3s ease-in-out;
animation-fill-mode: both;
`;
パフォーマンス最適化のコツ
アニメーションは美しいですが、パフォーマンスを考慮する必要があります。
ハードウェアアクセラレーションの活用
typescript// ✅ パフォーマンスが良い
const optimizedAnimation = keyframes`
from {
transform: translateX(0);
}
to {
transform: translateX(100px);
}
`;
// ❌ パフォーマンスが悪い
const unoptimizedAnimation = keyframes`
from {
left: 0;
}
to {
left: 100px;
}
`;
アニメーションの制御
typescriptconst ControlledAnimation = styled.div<{
isAnimating: boolean;
}>`
animation: ${(props) =>
props.isAnimating ? fadeIn : 'none'} 0.3s ease-out;
animation-fill-mode: both;
`;
// 使用例
const MyComponent = () => {
const [isAnimating, setIsAnimating] = useState(false);
useEffect(() => {
setIsAnimating(true);
const timer = setTimeout(
() => setIsAnimating(false),
300
);
return () => clearTimeout(timer);
}, []);
return (
<ControlledAnimation isAnimating={isAnimating}>
コンテンツ
</ControlledAnimation>
);
};
よくあるパフォーマンスエラー
エラー: 無限ループアニメーション
typescript// ❌ パフォーマンスが悪い
const infiniteAnimation = keyframes`
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
`;
const BadSpinner = styled.div`
animation: ${infiniteAnimation} 1s linear infinite;
`;
// ✅ パフォーマンスが良い
const GoodSpinner = styled.div`
animation: ${infiniteAnimation} 1s linear infinite;
will-change: transform;
`;
まとめ
Emotion の keyframes を使うことで、型安全で動的なアニメーションを実装できるようになりました。
この記事で学んだことを活かせば、ユーザーが「わあ、すごい!」と感動するようなアニメーションを作成できます。アニメーションは単なる装飾ではなく、ユーザー体験を向上させる重要な要素です。
実装する際は、パフォーマンスを意識し、適切なイージング関数を選び、ユーザーの期待に応えるアニメーションを心がけてください。
あなたのアプリケーションが、この記事で学んだアニメーション技術によって、より魅力的で使いやすいものになることを願っています。
関連リンク
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来