prefers-reduced-motion 支持
为前庭敏感或偏好用户禁用非必要动画。
无障碍入门
为什么
视差、spinner、滑入动画可引发前庭敏感用户恶心、头晕或癫痫。prefers-reduced-motion 是 OS 级信号调低 — 忽略它是 WCAG 2.3.3 违规。
何时使用
始终。任何超 5ms 动画须尊重 prefers-reduced-motion。是 5 行 CSS 守卫或一 hook 检查 — 无借口跳过。
如何做
1. CSS 守卫 — @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } }
2. JS hook — const reduce = useReducedMotion(); 真时传 { duration: 0 } 给 framer-motion
3. 保留必要动效 — 状态变化 150ms 淡入可;杀视差、旋转、弹跳
4. 别全禁 — 即时状态变化(opacity 0→1)可;仅杀装饰动效
5. 测试 — 切 OS 设置(macOS 系统 → 无障碍 → 显示 → 减弱动效),验证
6. 库支持 — framer-motion useReducedMotion、GSAP matchMedia、CSS 原生处理
7. 播报 — 若动效承载信息(进度条),用文本或静态指示替代
示例
/* 全局守卫 — 杀装饰动效,留即时状态变化 */
@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; /* 无平滑滚动 */
}
}
// framer-motion — 分支 transition
import { useReducedMotion } from "framer-motion";
function FadeIn({ children }: { children: React.ReactNode }) {
const reduce = useReducedMotion();
return (
<motion.div
initial={{ opacity: 0, y: reduce ? 0 : 8 }}
animate={{ opacity: 1, y: 0 }}
transition={reduce ? { duration: 0 } : { duration: 0.25, ease: "easeOut" }}
>
{children}
</motion.div>
);
}
常见问题
- ⚠杀所有动效含 opacity — 状态变化不可见,用户错过更新
- ⚠仅处理 CSS 非 JS 动画 — framer-motion 视差仍为减弱动效用户旋转
- ⚠无测试 — 从未切 OS 设置即发布,破损动效上线
最佳实践
- ✓留即时 opacity 变化 — 仅杀装饰动效(视差、旋转、弹跳、滑入)
- ✓动画库用 JS hook — framer-motion 的 useReducedMotion 分支 transition
- ✓切 OS 设置测试 — macOS 无障碍 → 显示 → 减弱动效,验证每屏