transition 干扰屏幕阅读器焦点流、键盘导航响应及系统动效偏好,需同步更新 ARIA 属性、禁用焦点样式动画、响应 prefers-reduced-motion 并彻底降级 JS 动画。

transition 会干扰屏幕阅读器的焦点流
当元素用 opacity 或 transform 做过渡隐藏/显示时,视觉上渐隐渐现,但屏幕阅读器仍可能在动画过程中尝试读取内容——尤其当 visibility: hidden 被错误使用(它不移除可访问树节点),或 aria-hidden 没同步更新。用户听到“正在加载…”刚念一半,内容就突然消失,造成语义断裂。
- 避免对
aria-live区域加 transition:动画延迟会让实时更新被跳过或重复播报 - 折叠面板展开时,别只靠
max-height过渡;必须同步设置aria-expanded="true",且在 transition 结束后才允许焦点进入子内容 - 用
display: none替代opacity: 0配合visibility: hidden做最终隐藏,确保可访问树彻底清理
动画触发与键盘导航冲突
hover 触发的 transition 在键盘用户(Tab 切换)下完全失效,而 focus 状态若也套用相同动画,会导致焦点框出现明显延迟——用户按 Tab 键后,焦点样式要等 0.2s 才出现,破坏操作节奏感。
- 对
:focus和:focus-visible单独声明无 transition 的样式,保证键盘操作即时响应 - 禁用
transition在outline、box-shadow等焦点提示属性上,这些本就不该动 - 如果必须动(比如高亮边框颜色),时长严格控制在
0.08s以内,否则人眼已感知为“卡顿”
强制动画违背用户系统偏好
现代操作系统(macOS、Windows、iOS)都提供“减少动态效果”开关,CSS 用 @media (prefers-reduced-motion: reduce) 可捕获该信号。但很多项目写了媒体查询,却只简单设 transition: none,结果 JS 仍继续触发类切换、重排、重绘——视觉不动了,CPU 却还在跑。
- 真正有效的写法是:
@media (prefers-reduced-motion: reduce) { * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } } - JS 中也要监听
matchMedia('(prefers-reduced-motion: reduce)'),跳过所有requestAnimationFrame动画逻辑 - 别只关 transition:
scroll-behavior: smooth、will-change、甚至transform的硬件加速都应一并降级
无障碍测试中 transition 最易被忽略的点
DevTools 的 Accessibility 面板看不出动画问题,得靠真实交互验证。最常漏掉的是:动画中断时的状态残留 + ARIA 属性不同步。
立即学习“前端免费学习笔记(深入)”;
- 用户快速连点“展开/收起”,JS 若没清除前一个
transitionend监听,max-height可能卡在 127px 这种中间值,导致内容截断且aria-expanded仍为true - 用
getComputedStyle(el).transitionDuration检查是否真被媒体查询覆盖,而不是依赖 class 名判断 - NVDA + Chrome 组合下,
opacity过渡中的元素仍会被读出,但 JAWS 可能直接跳过——跨工具测试不可省


















