定義
ユーザーが OS で「動きを減らす」を有効にしたとき、JavaScript で動かすページ遷移アニメーションをアプリ自身が止める 技法である。
prefers-reduced-motion: reduce は、その設定をブラウザが伝える CSS メディアクエリである。CSS transition/animation はトークンで duration をほぼ 0 にできるが、JS スプリング・MutationObserver ベースのライブラリ はその CSS 変数を読まない。そこで matchMedia で購読し、reduce なら遷移 Provider をマウントしないか transitions を空にする。
なぜ必要か
アクセシビリティ: 前庭器官に敏感なユーザー・注意散漫なユーザーに強制モーションは有害である。品質: ビジュアル回帰(VR)・axe スモークは reduced motion を強制することが多く、JS 遷移が動くと 中間フレームのスナップショット で flake になる。パフォーマンス: 仮想リストのように DOM が頻繁に変わるページで、遷移用 Observer がスクロール中の mutation を購読すると ホットパス税 になる。
動作原理
window.matchMedia('(prefers-reduced-motion: reduce)')を購読する。- React では
useSyncExternalStoreで SSR-safe に読む — サーバー/getServerSnapshotは通常false(モーション許可)、クライアント hydration 後に実値で補正する。設定はアニメーションだけを変え DOM テキストを変えなければ hydration mismatch はない。 - reduce なら (a) transitions 配列を空にするか (b) Provider 自体をレンダーしない — (b) は Observer コストまで除去する。
- ページ遷移設定で scroll を保持・操作しない(
preserveScroll: falseなど)。スクロール復元は Next・仮想リストの権限に残す。
実務での適用
- サイトクローム(ヘッダー/フッター)は遷移境界 の外 に置き、ルートごとに再アニメーションされないようにする。
- Playwright VR/a11y プロジェクトに
reducedMotion: 'reduce'を維持し、回帰を固定する。 - リスト↔詳細に幾何学的 drill 遷移を入れるときは、スクロール復元 e2e を先に 通してから差し替える。
- CSS View Transitions を使う場合も、reduced motion で animation-duration を 0 にするグローバルルールを併用する。
トレードオフ
| 選択 | 利点 | コスト |
|---|---|---|
| Provider 非マウント | Observer・スプリングコスト除去 | 分岐コード・テスト経路の増加 |
| transitions だけ空配列 | API が単純 | ライブラリが依然マウントされる可能性 |
| CSS だけリセット | 実装が容易 | JS 遷移には無効 |
使うべきでない場合
- 遷移ライブラリに scroll restoration まで任せ、仮想リストと同時に使う場合(権限衝突)。
- reduced motion ユーザー向けに「代替情報」が必要なのに、モーションだけ止めて状態変化を伝えない場合(モーション ≠ 情報伝達)。
よくある失敗
tokens.cssに--duration: 1msだけ入れて JS ライブラリはそのまま。- SSR で
window.matchMediaを直接呼んでサーバークラッシュ。 - 遷移レイヤー
key={pathname}とリスト scroll 復元が競合。
関連概念
- usesyncexternalstore-snapshot-identity — 外部ストア・メディア購読のスナップショット規則
- virtual-list-scroll-restore-authority — スクロールの単一権限
- css-view-timeline-entry-animation — CSS スクロール/ビュータイムラインのモーション