zudo-css

Type to search...

to open search from anywhere

prefers-reduced-motion(モーション軽減設定)

作成2026年3月13日更新2026年3月26日Takeshi Takatsudo

問題

アニメーションやトランジションは、前庭障害、モーション感度、特定の認知障害を持つユーザーに不快感、めまい、吐き気を引き起こす可能性があります。prefers-reduced-motion メディアクエリは、ユーザーがオペレーティングシステムの設定を通じてモーションの好みを伝えることを可能にします。AIエージェントは生成するコードにモーション設定の処理をほとんど含めず、含める場合でもすべてのモーションを完全に削除する傾向があります。しかし、これは有用な状態変化インジケーターまで削除してしまうため、逆にユーザビリティを損なう可能性があります。

解決方法

prefers-reduced-motion: reduce の設定を尊重し、モーションを削除するのではなく軽減しましょう。大きく速いアニメーションやパララックススタイルのアニメーションを、微妙なフェードや即時の状態変化に置き換えます。フォーカスリングやローディング状態のような機能的なインジケーターはそのまま維持しましょう。

2つのアプローチ

  1. モーション削除アプローチ:通常通りアニメーションを書き、prefers-reduced-motion: reduce ブロック内で無効化します。
  2. ノーモーションファーストアプローチ:デフォルトで静的なスタイルを書き、prefers-reduced-motion: no-preference ブロック内でアニメーションを追加します。この方が安全です。設定を指定していないユーザーでもモーションが軽減されるためです。
prefers-reduced-motion: フルモーション vs 軽減モーション

コード例

グローバルな軽減モーションリセット

モーション軽減を希望するユーザー向けに、すべてのアニメーションを軽減する防御的リセットです:

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

これは大まかなツールです。ベースラインとして使用し、必要に応じて個々のコンポーネントを調整しましょう。

モーションをフェードに置き換える(より良いアプローチ)

すべてのアニメーションを削除する代わりに、大きなモーションを微妙な透明度変化に置き換えます:

/* Default: slide-in animation */
.modal {
  animation: modal-enter 0.3s ease-out;
}

@keyframes modal-enter {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Reduced motion: fade only, no spatial movement */
@media (prefers-reduced-motion: reduce) {
  .modal {
    animation: modal-fade-in 0.2s ease-out;
  }

  @keyframes modal-fade-in {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
}

ノーモーションファーストアプローチ

アニメーションなしの状態から始め、モーション設定がないユーザーにのみアニメーションを追加します:

/* Base: static, no animation */
.card {
  opacity: 1;
  transform: none;
}

/* Only animate for users without motion preference */
@media (prefers-reduced-motion: no-preference) {
  .card {
    animation: card-reveal 0.4s ease-out both;
  }

  @keyframes card-reveal {
    from {
      opacity: 0;
      transform: translateY(20px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }
}

安全なトランジション

.button {
  background-color: var(--color-primary);
}

/* Hover transition: only for no-preference users */
@media (prefers-reduced-motion: no-preference) {
  .button {
    transition: background-color 0.15s ease, transform 0.15s ease;
  }
}

@media (hover: hover) {
  .button:hover {
    background-color: var(--color-primary-dark);
  }
}

/* Reduced motion users still see the color change, just instantly */

ローディングスピナーの代替

.spinner {
  width: 2rem;
  height: 2rem;
  border: 3px solid var(--color-border);
  border-top-color: var(--color-primary);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* Reduced motion: pulsing opacity instead of spinning */
@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation: pulse 1.5s ease-in-out infinite;
  }

  @keyframes pulse {
    0%,
    100% {
      opacity: 1;
    }
    50% {
      opacity: 0.4;
    }
  }
}

スクロール動作

html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

パララックスとスクロール駆動アニメーション

.hero__background {
  animation: parallax linear;
  animation-timeline: scroll();
}

@keyframes parallax {
  from {
    transform: translateY(-15%);
  }
  to {
    transform: translateY(15%);
  }
}

/* Disable parallax entirely for reduced motion */
@media (prefers-reduced-motion: reduce) {
  .hero__background {
    animation: none;
    transform: none;
  }
}

JavaScriptでの検出

JavaScriptで制御されるアニメーションの場合:

<script>
  const prefersReducedMotion = window.matchMedia(
    "(prefers-reduced-motion: reduce)"
  );

  function handleMotionPreference() {
    if (prefersReducedMotion.matches) {
      // Disable JS-driven animations
      document.documentElement.dataset.reducedMotion = "true";
    } else {
      delete document.documentElement.dataset.reducedMotion;
    }
  }

  prefersReducedMotion.addEventListener("change", handleMotionPreference);
  handleMotionPreference();
</script>
/* Use the data attribute for JS-controlled animations */
[data-reduced-motion="true"] .js-animated {
  animation: none !important;
  transition: none !important;
}

維持すべきもの vs 軽減すべきもの

/* KEEP: Focus indicators (functional, not decorative) */
.button:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
  /* No transition needed — instant is fine */
}

/* KEEP: Color changes (not spatial motion) */
@media (prefers-reduced-motion: reduce) {
  .button:hover {
    /* Color change is fine, remove transform */
    background-color: var(--color-primary-dark);
    transform: none;
  }
}

/* REDUCE: Large spatial movement */
@media (prefers-reduced-motion: reduce) {
  .slide-in-panel {
    /* Replace slide with fade */
    animation: fade-in 0.15s ease;
  }
}

/* REMOVE: Parallax, background movement, continuous animations */
@media (prefers-reduced-motion: reduce) {
  .background-animation,
  .parallax-layer,
  .floating-element {
    animation: none;
  }
}

AIがよくやるミス

  • prefers-reduced-motion を一切含めない:最もよくあるミスです。AIはモーション設定の処理なしでアニメーションを生成します。
  • 一括ルールですべてのアニメーションを削除する:すべてのアニメーションとトランジションを無効にすると、有用な状態インジケーターまで削除されます。モーションは削除ではなく軽減しましょう。
  • scroll-behavior: auto を忘れる:軽減モーションユーザー向けのオプトアウトなしで scroll-behavior: smooth を設定してしまいます。
  • 削除したアニメーションの代替を提供しない:スライドインアニメーションを削除しながら、フェードの代替を提供せず、ユーザーに状態変化のインジケーターがなくなります。
  • CSSアニメーションのみ対応する:JavaScriptで制御されるアニメーション(GSAP、Framer Motionなど)もこの設定を尊重する必要があることを忘れてしまいます。
  • デフォルト状態のみテストする:軽減モーションを有効にした場合の体験を検証しません。Chrome DevToolsでエミュレートできます:Rendering パネル > Emulate CSS media feature > prefers-reduced-motion: reduce。

使い分け

  • アニメーションのあるすべてのプロジェクト:アニメーションやトランジションを追加する場合は、必ず prefers-reduced-motion の処理を追加しましょう。
  • パララックスとスクロールエフェクト:軽減モーションユーザーに対しては常に無効にすべきです。
  • 自動再生アニメーション:フローティング要素や背景エフェクトなどの継続的な装飾アニメーションは停止すべきです。
  • ページトランジション:フルページのルートトランジションはシンプルなフェードに軽減するか、削除すべきです。
  • 機能的なモーションは維持する:ローディングインジケーター、フォーカスリング、状態変化インジケーターは保持しましょう(簡略化は可能ですが、削除は避けましょう)。

参考リンク

Revision History