Back

使用 prefers-reduced-motion 实现无障碍动画

使用 prefers-reduced-motion 实现无障碍动画

部分用户在浏览动画界面时会出现眩晕、恶心或方向感丧失等不适。prefers-reduced-motion 这一 CSS 媒体特性可让你检测用户的系统偏好设置,并以更安全的方式响应——同时不会让 UI 失去全部视觉精致感。本文将介绍在常见组件模式中实施 CSS 与 JavaScript 的实用方法。

核心要点

  • prefers-reduced-motion 是一个读取操作系统级无障碍设置的 CSS 媒体查询,有两个值:reduceno-preference
  • 目标是减少或替换非必要动效,而非完全去除动画——透明度淡入淡出和缩短动画时长仍是更安全的替代方案。
  • 使用渐进式(opt-in)CSS 方法,可保护那些尚未明确设置偏好但仍可能对动效敏感的用户。
  • 对于 JavaScript 驱动的动画,可使用 window.matchMedia 及类似 Motion 的 useReducedMotion() Hook 等监听器,使行为与系统变化保持同步。
  • Chrome DevTools 的 Rendering 面板可模拟此偏好设置,便于测试而无需更改操作系统设置。
  • prefers-reduced-motion 有助于满足 WCAG 2.3.3 要求,但不涵盖自动播放或循环内容——根据 WCAG 2.2.2,这类内容仍需提供显式的暂停控件。

prefers-reduced-motion 究竟做什么

prefers-reduced-motion 是一个成熟且广受支持的 CSS 媒体查询——并非新 API。它读取用户在操作系统(macOS、Windows、iOS、Android、Linux)上启用的系统级无障碍设置。两个值分别是:

  • reduce——用户请求减少动效
  • no-preference——未设置偏好

值得注意的是:@media (prefers-reduced-motion)@media (prefers-reduced-motion: reduce) 的简写等价形式。

MDN Web Docs 条目涵盖完整语法和浏览器兼容性表,而 Can I use 也证实主流现代浏览器引擎对其支持广泛。

正确的思维模型:减少,而非移除

一个常见误区是把它当作所有动画的总开关,但这并非目标。包括 WCAG 2.3.3(“交互产生的动画”,AAA 级)在内的相关指南都建议减少或替换非必要动效,特别是:

  • 大幅度运动(视差、缩放、滑动面板)
  • 旋转或转动的元素
  • 让内容穿越视口的滚动触发动画

透明度淡入淡出、颜色过渡和缩短时长通常是更安全的替代方案。例如,一个以淡入方式出现而非从底部飞入的模态框,依然能传达状态变化,同时不会引发前庭不适。

CSS 实现:两种方法

防御式(opt-out): 默认定义动画样式,然后在媒体查询内覆盖。

.modal {
  transform: translateY(20px);
  opacity: 0;
  transition: transform 300ms ease, opacity 300ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .modal {
    transform: none;
    transition: opacity 200ms ease;
  }
}

渐进式(opt-in): 默认定义静态样式,仅当用户未选择减少动效时才添加动画。

/* 默认静态 */
.modal {
  opacity: 0;
  transition: opacity 200ms ease;
}

@media (prefers-reduced-motion: no-preference) {
  .modal {
    transform: translateY(20px);
    transition: transform 300ms ease, opacity 300ms ease;
  }
}

对于尚未设置偏好但仍可能对动效敏感的用户而言,渐进式方法更安全。

CSS 自定义属性让该方案在大型代码库中具备良好的可扩展性:

:root {
  --duration: 300ms;
  --easing: ease;
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --duration: 0.01ms;
  }
}

.drawer {
  transition: transform var(--duration) var(--easing);
}

在 JavaScript 中检测偏好

对于 JS 驱动的动画,使用 window.matchMedia

const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)');

if (prefersReduced.matches) {
  // 跳过或简化动画
}

// 响应偏好的实时变化(例如用户切换操作系统设置)
prefersReduced.addEventListener('change', (e) => {
  if (e.matches) {
    // 暂停或替换动画
  }
});

这对轮播、滚动触发效果或任何非 CSS 驱动的动画都非常有用。

动画库:Framer Motion 与 Motion.dev

如果你使用 Motion for React(许多人仍称其为 Framer Motion——其文档现已迁至 Motion.dev),useReducedMotion() Hook 可以优雅地处理这一需求:

import { motion } from "motion/react";
import { useReducedMotion } from "motion/react";

function Drawer({ isOpen }) {
  const reduce = useReducedMotion();

  return (
    <motion.div
      animate={{ x: isOpen ? 0 : -300, opacity: isOpen ? 1 : 0 }}
      transition={reduce ? { duration: 0 } : { duration: 0.3 }}
    />
  );
}

该 Hook 以响应式方式读取系统偏好,因此即便用户在会话中途更改操作系统设置,它也能保持同步。

使用 Chrome DevTools 进行测试

你无需更改操作系统设置即可测试。在 Chrome DevTools 中:

  1. 打开 DevTools → Rendering 标签(通过三点菜单 → More toolsRendering
  2. 找到 “Emulate CSS media feature prefers-reduced-motion”
  3. 将其设置为 reduce

页面会立即响应,让你能够在不调整系统偏好的情况下验证每个动画组件。

关于 WCAG 覆盖范围的说明

prefers-reduced-motion 有助于满足 WCAG 2.3.3,但它并不能覆盖所有情况。自动播放视频、循环 GIF 以及持续运动的内容,根据 WCAG 2.2.2(“暂停、停止、隐藏”)仍可能需要显式的暂停/停止控件。该媒体查询能很好地处理由交互触发的动画——而持续性的背景动效则需要独立的解决方案。

实用建议

审查你的动画组件——模态框、抽屉、悬停效果、轮播、页面过渡——并针对每个组件决定是缩短时长、用透明度替换运动,还是完全跳过动画。使用 CSS 自定义属性集中管理逻辑,在 JavaScript 控制动画时使用 matchMedia,并通过 Chrome DevTools 验证而无需更改操作系统。

总结

尊重 prefers-reduced-motion 是前端开发者能获得的投入最低、收益最高的无障碍改进之一。该技术已经稳定,浏览器支持极佳,并且在 CSS、JavaScript 和现代动画库中的实现模式都很直观。真正的工作在于转变你的思维模型:动画应被视为面向敏感用户可调低的图层,而非一个可一键关闭的功能。请将这种习惯融入你交付的每一个组件。

常见问题

通常更建议将时长设为 0.01ms 这样的极小值,而不是零。这样可以保留 transitionend 事件,监听动画完成的 JavaScript 逻辑能够继续触发。真正的零值在某些浏览器中会跳过该事件,从而破坏依赖它的状态机。视觉效果与瞬时变化完全相同。

可能会,但行为因浏览器和实现而异。更安全的做法是将 CSS 平滑滚动包裹在 @media (prefers-reduced-motion: no-preference) 内,这样选择减少动效的用户始终会得到瞬时滚动。如果你在 JavaScript 中实现自定义滚动动画,也应手动检测该偏好,并使用 window.scrollTo(将 behavior 设置为 auto)作为回退方案。

不会。更短或跳过的动画通常会提升感知响应速度,因为用户能更快地看到内容稳定。Interaction to Next Paint 往往会受益,因为过渡不再延迟视觉反馈。唯一需要注意的是:如果移除的动画原本用于传达状态变化,请用透明度淡入淡出或颜色变化替代,以保证界面依然显得灵敏而非突兀。

可以,对于使用共享设备或尚未发现操作系统选项的用户而言,这是一种良好做法。将偏好存储在 localStorage 中,并使用逻辑或运算符将其与媒体查询结果结合。这样,操作系统设置或应用内开关任一即可触发减少动效,让用户拥有最大的控制权,同时不会覆盖其系统偏好。

Truly understand users experience

See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..

OpenReplay