T-CREATOR

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

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 を使うことで、型安全で動的なアニメーションを実装できるようになりました。

この記事で学んだことを活かせば、ユーザーが「わあ、すごい!」と感動するようなアニメーションを作成できます。アニメーションは単なる装飾ではなく、ユーザー体験を向上させる重要な要素です。

実装する際は、パフォーマンスを意識し、適切なイージング関数を選び、ユーザーの期待に応えるアニメーションを心がけてください。

あなたのアプリケーションが、この記事で学んだアニメーション技術によって、より魅力的で使いやすいものになることを願っています。

関連リンク