先记住这个答案
prefers-reduced-motion: reduce 表示用户希望减少动态效果,但并不等于要求一帧动画都没有。直接写 transition: none 或 animation: none 会让状态瞬间跳变,用户失去了「这个东西正在打开/关闭」的反馈。更稳妥的做法是保留过渡声明,只把时长缩短到接近零,或者把大幅位移替换成透明度渐变。这样既避免了前庭障碍患者的不适,又保持了界面可理解。
reduce表示希望减少动效- 可缩短或替换易引发不适的动效
- 平移、缩放容易引起眩晕,透明度变化通常安全
- 关键帧动画与过渡都要检查
哪些动画容易引起不适
前庭障碍患者对空间运动最敏感。页面级的滑入滑出、视差滚动、元素旋转放大都可能引发眩晕。相比之下,透明度从 0 到 1 的变化、颜色的过渡几乎不会造成问题。
所以处理 prefers-reduced-motion 时,不需要一刀切。把 transform 相关的过渡去掉或缩短就行,opacity 的淡入淡出可以留着。
.modal {
/* 默认:从下方滑入 + 淡入 */
transition: transform 300ms ease, opacity 300ms ease;
}
@media (prefers-reduced-motion: reduce) {
.modal {
/* 去掉位移,保留淡入,时长缩到几乎瞬间 */
transition: opacity 0.01s ease;
transform: none;
}
}这段代码在用户开了减少动效后,不再播放滑入动画,但透明度还是有一个极短的过渡。视觉上几乎是瞬间出现,但浏览器仍然走了过渡流程,transitionend 事件照常触发,不会影响依赖这个事件的 JavaScript 逻辑。
@keyframes 动画的处理方式
很多人只覆盖了 transition,忘了 @keyframes 动画。比如一个无限旋转的 loading 图标,在减弱动效模式下应该停下来或者换成脉冲式的透明度闪烁。
直接写 animation: none 会导致元素卡在初始帧。如果初始帧的样式和你期望的静止状态不一样,界面就会出问题。更可靠的写法是用 animation-duration: 0.01s 配合 animation-iteration-count: 1,让动画跑到终态然后停住。
.spinner {
animation: spin 1s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
.spinner {
/* 不旋转,改为一次性淡入后停住 */
animation: fade-in 0.01s ease forwards;
}
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}这样 spinner 在减弱动效模式下不会旋转,也不会因为 animation: none 丢失样式。
容易答错的地方
- 以为写了
transition: none就万事大吉 - 很多代码里有 JavaScript 监听
transitionend来触发下一步操作。如果过渡被彻底去掉,transitionend不会触发,那段逻辑就断了。把时长缩短到接近零,事件仍然会正常触发,不会破坏已有逻辑。 - 只处理 CSS 没管 JavaScript 动画
prefers-reduced-motion是一个媒体查询,CSS 里生效了不等于 JavaScript 动画也停了。用requestAnimationFrame或第三方动画库做的运动需要在 JavaScript 里用matchMedia('(prefers-reduced-motion: reduce)')单独检测,然后跳过或缩短动画。- 把所有动效一刀切掉
- 面试时如果说「全部写
none就行」,面试官会追问。减弱动效的目标是避免眩晕,不是让界面变成幻灯片。保留不引起不适的微动效,反而能拿到加分。
面试官还会怎么问?
怎么在 JavaScript 里检测用户是否开了减少动效?
用 window.matchMedia('(prefers-reduced-motion: reduce)') 拿到一个 MediaQueryList 对象,读它的 matches 属性就知道当前状态。还可以监听它的 change 事件,用户在系统设置里切换时实时响应。
prefers-reduced-motion 有几个值?
只有两个:no-preference 表示用户没有特别设置,reduce 表示用户希望减少动效。没有 increase 或 none 这样的值。
如果项目里有上百处动画,怎么统一管理?
把动画时长提取成 CSS 自定义属性,比如 --duration-normal: 300ms。在 prefers-reduced-motion: reduce 的媒体查询里把它改成 0.01s,所有引用这个变量的地方一起生效,不用逐条覆盖。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。