必须将transition和animation写在基础选择器中,否则@media (prefers-reduced-motion: reduce)无法生效;因浏览器仅在起始与结束态均声明同一transition时才启用插值,若仅在:hover等状态类中定义,移出时无声明可回退,导致跳变。

必须把 transition 和 animation 声明写在基础选择器里,否则 @media (prefers-reduced-motion: reduce) 无法真正生效。
为什么 @media (prefers-reduced-motion: reduce) 没起作用
常见错误是只在 :hover、.is-open 或第三方组件类(如 .ant-collapse-content-active)里写 transition,然后指望媒体查询能“覆盖”它。结果是:鼠标移入时有动画,移出时直接跳变——因为基础状态没声明 transition,浏览器无从插值,@media 根本没机会介入。
根本原因在于 CSS 插值机制:只有起始态和结束态都存在同一组 transition 声明时,浏览器才启用平滑过渡;否则就是硬切。
- 写成
@media (prefers-reduced-motion: true)是语法错误,该媒体查询只接受reduce或no-preference - 漏引号、大小写错误(如
Reduce)、空格不规范,都会导致整条媒体查询被浏览器忽略 - 在 DevTools 的 Styles 面板里,如果媒体查询块灰掉或未高亮,基本可以判定是值写错了
哪些 CSS 属性必须一并禁用
光设 animation: none 和 transition: none 不够。只要参与了视觉运动,以下属性在 reduce 模式下也得强制归零或重置:
立即学习“前端免费学习笔记(深入)”;
-
transform:平移/缩放/旋转,最常用也最容易被忽略;需在媒体查询中加transform: none !important -
opacity:渐隐渐显虽轻量,但快速切换仍可能诱发眩晕;若需保留反馈,改用transition: opacity 0.1s而非完全关闭 -
width/height:max-height过渡常用于折叠菜单,建议保留transition: max-height 0.1s linear,避免内容被截断 -
scroll-behavior:需额外加html { scroll-behavior: auto !important; }禁用平滑滚动
怎么避免破坏功能的“粗暴清空”
全局写 * { animation: none !important; transition: none !important; } 看似省事,实则会让模态框闪现、下拉菜单突然撑开、骨架屏残留空白——这不是适配,是功能阉割。
正确做法是分层控制:
- 对纯装饰性动画(轮播、视差、弹窗入场),用
animation: none或animation-duration: 0.01ms替代 - 对结构依赖型过渡(折叠面板、提示气泡),保留极短时长的
transition,例如transition: max-height 0.1s, opacity 0.1s - 对 JS 直接操作的
transform或opacity,即使没写transition,浏览器仍会隐式插值;必须在媒体查询中强制重置:transform: none !important; opacity: 1 !important; - 第三方组件库(如 Ant Design、MUI)若把
transition写在动态类里,需手动补基础规则或覆盖其 CSS
JS 动画不能靠 CSS 媒体查询自动降级
@media (prefers-reduced-motion: reduce) 只影响 CSS 声明,对 requestAnimationFrame、GSAP、轮播自动播放、骨架屏等完全无效。
必须手动监听并响应:
- 初始化时获取状态:
const motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)") - 监听变化(Safari 14+ 支持):
motionQuery.addEventListener("change", handleMotionChange) - 动画启动前判断:
if (!motionQuery.matches) { startParallax() },不要运行中暂停再恢复 - 对骨架屏等场景,直接设终态:
el.style.opacity = "1"; el.style.transform = "none";
最容易被忽略的是:CSS 媒体查询无法干预内联样式或 JS 动画逻辑,而用户开启“减少运动”后,系统设置是实时生效的——这意味着 JS 监听必须可靠,且降级动作要发生在 DOM 更新之前。


















