直接写 animation: none 无效,因其仅清空当前声明,无法覆盖父级、inline style 或第三方样式;须用高权重重置,如 @media (prefers-reduced-motion: reduce) 统一禁用动画并补全终态样式。

为什么直接写 animation: none 不管用
很多人在媒体查询里加 @media (max-width: 480px) { .card { animation: none; } },结果动画照常跑——因为 animation: none 只清空当前声明,无法覆盖父级或 inline style 里的 animation 值,更没法干预第三方组件注入的样式。真正要生效,得用更高权重、更彻底的重置方式。
用 @media (prefers-reduced-motion: reduce) 覆盖所有动画入口
这不是“为低性能手机”定制的 hack,而是操作系统级信号:iOS 低电量模式、Android 辅助功能开关、macOS 可访问性设置都会触发它。Safari、Chrome、Firefox 全部原生支持,且比任何 JS 性能探测都可靠。
- 必须写成
@media (prefers-reduced-motion: reduce),true或on是无效值,整个块会被浏览器忽略 - 在该块内,不仅要设
animation: none和transition: none,还得补上终态样式,比如transform: translateX(0)、opacity: 1,否则动画被禁后元素可能卡在中间态 - 别只改 class,还要处理 inline style:JS 动画若用
el.style.animation = '',得同步检查matchMedia('(prefers-reduced-motion: reduce)').matches,否则用户开了系统设置,你的 JS 还在拼命 requestAnimationFrame
小屏设备主动降级,不是等它卡了再救
很多低端安卓机(如 Android 6–7 的 WebView)根本撑不住连续 transform 插值,哪怕只用 translate 和 opacity。这时候靠系统偏好已经晚了,得提前按设备能力分层。
- 用
@media (max-width: 480px)或@media (max-resolution: 1.5dppx)拦截老款小屏设备,把复杂关键帧简化为两帧硬切:animation: fade-in 0.1s step-end - 禁用
transform-style: preserve-3d和perspective——它们在移动端 WebKit 渲染器里极易引发图层爆炸和白屏 - 避免给列表项批量加
will-change: transform,一屏 20 个卡片就可能触发 OOM,低端机直接卡死
JS 动画必须监听 matchMedia 并同步状态
纯 CSS 降级只能管住样式层,JS 驱动的动画(比如滚动触发动画、弹窗入场)会继续执行,但 animationend 或 transitionend 事件永远不会触发,导致状态错乱。
- 初始化时就读取
window.matchMedia('(prefers-reduced-motion: reduce)'),它的.matches属性就是当前系统状态 - 绑定
change事件,在用户切换系统设置时立刻更新 DOM class,比如加reduced-motion到根节点 - 所有 JS 动画逻辑开头加判断:
if (reducedMotion) { el.style.opacity = '1'; el.style.transform = 'none'; return; },别指望 CSS 自己“接管”
实际最难的不是写对那几行媒体查询,而是让整条链路——CSS、JS、第三方库、inline style、服务端渲染输出——全部对同一个信号做出一致响应。漏掉任意一环,用户就会看到动画突然消失、按钮点击没反馈、或者开屏画面卡在半透明状态。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











