应使用@media (prefers-reduced-motion: reduce)直接在CSS中降级动画,因其在样式计算阶段即生效、兼容性好、语义准确;避免JS检测导致FOUC、监听失效或无法控制内联样式等问题。

直接用 @media (prefers-reduced-motion: reduce) 就行,别绕弯写 JS 判断或 UA 识别——这是唯一被所有现代浏览器稳定支持、语义准确、且在 CSS 计算阶段就生效的方案。
为什么不能用 JS 检测来关动画
CSS 动画降级必须发生在样式计算和布局之前。JS 读取 window.matchMedia('(prefers-reduced-motion: reduce)') 是运行时行为,会导致:
- FOUC(页面闪一下动画才停),尤其在 Next.js / Nuxt 等 SSR 场景下明显
- 用户中途开关系统设置时,JS 监听可能延迟或漏触发(Safari 14 以下不支持
change事件) - 旧版安卓 WebView 可能根本没实现
matchMedia,直接报错或返回false - 第三方组件(如 Ant Design、Element Plus)里内联的
animation不受 JS 控制,CSS 层面才真正兜得住
怎么写才不破坏功能又真有效
关键不是“一刀切禁掉所有动画”,而是让结构逻辑照常、视觉动效降级。常见错误是写成:
@media (prefers-reduced-motion: reduce) {
* { animation: none !important; }
}这会覆盖组件库内部的焦点管理、滚动容器高度过渡等必要行为。推荐做法:
立即学习“前端免费学习笔记(深入)”;
- 把每个带
transition或animation的选择器单独包裹进媒体查询里 - 对纯视觉动画(如背景色轮播、loading-spinner)设
animation: none - 对结构相关过渡(如菜单展开的
max-height、模态框显隐)保留opacity或visibility的瞬时切换 - 用
transition: opacity 0.01s替代transition: all 0.2s,避免误触 transform/box-shadow 等重绘属性
transform 和 text-shadow 这类隐式动画也要管
很多人只改 @keyframes,却忘了 hover 时的 transform: scale(1.05) 或 text-shadow 变化也会触发平滑插值。低端机上这些反而更卡,因为不是 GPU 加速属性。
- 所有含
transform的transition都要显式重置:.btn:hover { transform: scale(1); transition: none; } -
text-shadow动效必须配媒体查询:@media (prefers-reduced-motion: reduce) { h2 { transition: none; text-shadow: 0 0.1em 0.2em rgba(0,0,0,0.1); } } - 慎用
will-change: transform保帧率——在低配设备上强制合成可能加重负担,will-change: auto更安全
兼容性与测试要点
prefers-reduced-motion 在 Chrome 74+、Firefox 63+、Safari 13+/iOS 13+、Edge 79+ 均原生支持。旧版本会忽略该查询,属于安全降级(不报错、不生效)。
- 开发时在 Chrome DevTools → Rendering 面板勾选 “Reduced motion” 快速模拟
- 真机测试重点看 iOS Safari:它对系统设置响应最严格,连自定义滚动条动画都会静音
- 别信
@media (prefers-reduced-motion: no-preference)——它不代表“可以开动画”,只是未设置,不能当 fallback 使用 - 第三方动画库(如 Animate.css)需手动加媒体查询包裹,它的类名不会自动适配
最容易被忽略的是:JS 驱动的动画(比如 requestAnimationFrame 轮播、Canvas 粒子)完全不受 CSS 媒体查询影响,必须单独监听并跳过执行——但那是另一层事,CSS 层面先得干净利落。


















