prefers-reduced-motion 是操作系统级媒体查询,用于响应用户“减少运动”偏好;不能仅靠 CSS 全局禁动画,否则会破坏模态框焦点、菜单可滚动性等关键功能。
prefers-reduced-motion 是一个媒体查询特性,由操作系统或浏览器设置触发(比如 macOS 的“减少运动”、Windows 的“显示设置→动画效果”、iOS 的“辅助功能→动态效果”),它返回 reduce 或 no-preference。很多人误以为只要在 CSS 里加个 @media (prefers-reduced-motion: reduce) { * { animation: none !important; } } 就万事大吉——但这样会粗暴禁用所有动画,包括那些对功能至关重要的过渡(比如模态框淡入后焦点自动捕获、菜单展开时的 height 过渡保障可滚动性),反而破坏可访问性。
关键不是“关掉动画”,而是“降级为无障碍友好的行为”。优先使用 transform 和 opacity 做瞬时切换,避免依赖时间轴的复杂关键帧;对必须保留的过渡,缩短时长并禁用位移类变化。
transition 替代 animation:更易被媒体查询精准控制max-height 过渡(结构相关),背景色变化用 animation(纯视觉)@media (prefers-reduced-motion: reduce) 中只重置动画属性,不碰布局和焦点逻辑:@media (prefers-reduced-motion: reduce) { * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01s !important; } /* 保留 opacity 过渡,确保元素不会突兀出现 */ [aria-expanded="true"], .is-open { opacity: 1; transition: opacity 0.1s ease; }}
仅靠 CSS 无法处理 JS 驱动的动画(如 requestAnimationFrame 滚动视差、Canvas 动画、第三方库的轮播)。必须主动读取 matchMedia 状态,并在初始化和运行时响应变化。
window.matchMedia("(prefers-reduced-motion: reduce)").matches 判断初始状态matchMedia 支持 addEventListener("change", handler)(注意 Safari 旧版需用 addListener)if (!reducedMotion) { startAutoSlide() },而不是在 reduced 模式下强行 pause 后又 resumescrollIntoView({ behavior: "smooth" }),改用 scrollIntoView()(无 behavior 参数)示例:
立即学习“前端免费学习笔记(深入)”;
const motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");function handleMotionChange(e) { if (e.matches) { // 关闭 canvas 动画循环 cancelAnimationFrame(animationId); // 禁用自动轮播 carousel.stop(); } else { carousel.start(); }}motionQuery.addEventListener("change", handleMotionChange);// 初始化handleMotionChange(motionQuery);
无障碍核心原则是“不牺牲功能换安静”。以下动画即使在 reduce 模式下也应保留,或至少提供等效替代:
:focus-visible 的 outline 动画:这是键盘导航的生命线,禁用等于切断键盘用户路径max-height + overflow: hidden 实现,比 animation 更可控loading... 文本替换):纯文本变化无需动画,但若用旋转图标,应替换为静态图标 + ARIA live region 通知真正该被降级的,是那些纯装饰性的、高频闪烁的、或引发前庭障碍风险的动画——比如背景粒子、无限滚动波纹、自动跳转的幻灯片。