prefers-reduced-motion是检测用户系统“减弱动态效果”偏好的CSS媒体特性,必须用@media (prefers-reduced-motion: reduce)显式重置animation/transition等属性并加!important,否则动画不会真正停止;还需同步降级JS动画、伪类状态及焦点样式以满足可访问性要求。

prefers-reduced-motion 是什么,为什么不能只加@media就完事
prefers-reduced-motion 是一个 CSS 媒体查询特性,用于检测用户是否在系统级开启了“减弱动态效果”(macOS / iOS / Windows 都支持)。它不是装饰性开关,而是 WCAG 2.1 和 EN 301 549 强制要求的可访问性信号。很多开发者加了 @media (prefers-reduced-motion: reduce) 就以为达标,结果上线后被无障碍审计打回——因为没验证实际行为:动画是否真停了?焦点环是否还抖?过渡是否仍触发?更关键的是,reduce 状态下,浏览器不会自动禁用你的 animation 或 transition,你必须显式覆盖。
怎么写才真正生效:覆盖策略与常见错误
生效的核心是「主动重置」,而不是「被动忽略」。下面这些写法看似合理,实则无效或危险:
-
animation: none可能清不掉已声明的animation-name,尤其当它来自第三方库或 CSS-in-JS 注入时 - 只写
transition: none不够,因为transform、opacity的瞬时变化仍可能造成视觉突兀 - 漏掉伪类状态,比如
:hover、:focus、:active下的过渡仍会执行
推荐写法是统一降级为 opacity 变化 + transform 瞬移(无缓动),例如:
button {
transition: opacity 0.1s, transform 0.1s;
}
@media (prefers-reduced-motion: reduce) {
button {
transition: opacity 0.01s !important;
/* 移除所有 transform 动画,强制静止 */
}
button:hover {
transform: none !important;
}
}哪些动画必须降级,哪些可以保留
WCAG 明确要求:所有非功能性的运动(位移、缩放、旋转、路径动画)必须降级;但以下情况可保留原逻辑:
立即学习“前端免费学习笔记(深入)”;
- 表单输入框的
:focus边框高亮(只要不伴随晃动/弹跳) - 加载指示器(spinner)属于功能性反馈,可用
opacity脉冲代替旋转 - 模态框淡入(
opacity+visibility)可保留,但禁止配合top或scale
特别注意:JavaScript 触发的 element.animate() 也必须监听 window.matchMedia('(prefers-reduced-motion: reduce)') 并提前 return,否则 CSS 降级无效。
测试环节最容易被跳过的三件事
光写对代码不等于适配成功,真实环境里这几个动作缺一不可:
- 在 macOS 系统设置 → 辅助功能 → 显示 → 开启「减弱动态效果」,然后在 Safari / Chrome 中硬刷新页面
- 用 Windows 设置 → 轻松使用 → 显示 → 关闭「在 Windows 中显示动画」,再检查
:active按钮是否还有压感缩放 - 运行
window.matchMedia('(prefers-reduced-motion: reduce)').matches确认 JS 层拿到的值与 CSS 媒体查询一致——有些旧版 Electron 或 WebView 会返回false即使系统已开启
最常被忽略的点是:动效降级后,焦点管理是否依然清晰?比如去掉 outline 又没补 box-shadow,键盘用户就彻底找不到当前聚焦项了。


















