必须在基础选择器中声明 transition 或 animation,再用 @media (prefers-reduced-motion: reduce) 覆盖为 none;若仅在 :hover 等状态中声明,起始态缺失导致插值失效,媒体查询无法介入。

直接用 @media (prefers-reduced-motion: reduce) 包裹动画降级规则,不是布尔值,不能写成 true 或 reduce(首字母大写)——写错就完全不生效。
为什么 @media (prefers-reduced-motion: reduce) 没反应?
最常见原因是:只在 :hover、.is-active 或第三方组件类(比如 .ant-collapse-content-active)里声明了 transition 或 animation,基础状态没写。浏览器插值需要起始态和结束态都存在同一组过渡声明,否则移出时直接跳变,媒体查询根本没机会介入。
其他典型错误包括:
-
@media (prefers-reduced-motion: true)—— 语法非法,该特性只接受reduce或no-preference -
Reduce或reduce(末尾空格)—— 值不匹配,整条媒体查询被忽略 - 在 DevTools 的 Styles 面板里,该媒体查询块灰掉或未高亮,基本可判定是值写错
怎么写才真正生效?
必须把 transition 和 animation 声明放在基础选择器里,再用媒体查询覆盖。例如按钮的悬停动效:
立即学习“前端免费学习笔记(深入)”;
button {
transition: background-color 0.2s ease, transform 0.2s ease;
}
button:hover {
background-color: #2980b9;
transform: translateY(-2px);
}
<p>@media (prefers-reduced-motion: reduce) {
button {
transition: none;
}
}注意三点:
- 不要只改
@keyframes,transform + transition这类隐式动画同样要重置 - 避免写成
* { animation: none !important }—— 全局强制覆盖会破坏组件库内部逻辑,调试困难 - 所有带动画的交互点都要覆盖:自动轮播、页面切换、下拉展开、悬停反馈等
要不要监听 JS 并加 class?
可以,但不是必须。纯 CSS 方案已覆盖绝大多数场景。如果需要更精细控制(比如禁用某个 JS 动画库的自动播放),可以用 window.matchMedia:
const motionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
document.documentElement.classList.toggle('reduced-motion', motionQuery.matches);
<p>motionQuery.addEventListener('change', e => {
document.documentElement.classList.toggle('reduced-motion', e.matches);
});这样你就能在 CSS 里写 .reduced-motion .carousel { animation: none; },但注意:JS 监听不能替代 CSS 媒体查询,因为 SSR 或首屏渲染时 JS 还没执行,CSS 层面的降级必须先行。
最容易被忽略的是「基础态声明」——很多人以为只要在媒体查询里写 transition: none 就够了,结果发现 hover 还在滑,移出就跳。其实问题不在媒体查询,而在它之前没给元素一个可插值的起点。


















