必须主动中断进行中的 transition,否则新状态会覆盖旧动画时间线,导致视觉抽搐、跳变或“呼吸式闪烁”;transitionend 不可靠,应使用 offsetwidth 强制重排来中断,transition 需写在常态选择器中,避免用 display: none,确保合成层启用。

必须打断进行中的 transition,否则新状态会覆盖旧动画时间线,导致视觉抽搐、跳变或“呼吸式闪烁”。
transitionend 事件不可靠,得主动中断
浏览器不会自动取消正在运行的 transition,transitionend 在快速连续触发时大概率不触发——比如鼠标在菜单项间快速划过,中间几次 hover 状态根本没走完,事件监听就失效了。
- 别依赖
transitionend来清理状态,它只是“完成通知”,不是“中断钩子” - 每次新交互开始前,先用
void element.offsetWidth强制重排,这是最轻量、最可靠的中断方式 - 如果元素已处于目标态,
offsetWidth仍有效:它让浏览器同步计算当前布局,为下一次样式变更建立全新起始快照
transition 必须写在常态选择器里
把 transition 塞进 :hover 或 .active 规则里,等于告诉浏览器:“只在悬停时才启用过渡”。一旦鼠标移开,规则消失,过渡上下文立刻清空,结果就是“进有动画、出直接跳”。
- 正确写法:
.nav-item { transition: transform 0.2s, opacity 0.15s; },再单独写.nav-item:hover { transform: translateY(-2px); } - 用 DevTools 的
Computed面板确认:transition-property和transition-duration在常态和悬停态下都存在,且值非none - 避免写
transition: all 0.3s——它会让margin、border-color这类无法合成的属性也参与过渡,拖垮整条链
高频区域要加防抖 + pointer-events 控制
导航栏、标签页、卡片网格这类用户可能毫秒级进出的区域,光靠 CSS 无法应对。JS 层必须介入,但不能粗暴节流。
- 简单防抖:用
setTimeout(() => el.classList.toggle('is-hovered'), 50),50ms 是多数人感知不到延迟又足够过滤抖动的阈值 - 动画中临时禁用事件:
el.style.pointerEvents = 'none',等requestAnimationFrame后再恢复,防止鼠标进出堆积 - 别用
display: none切换来“重置”——它不可插值,transition 根本不启动;改用opacity: 0+visibility: hidden组合
合成层没起来,transition 就是纸老虎
即使写了 transform 和 opacity,如果没真正进入 GPU 合成管线,高刷屏上照样抖。关键看 Layers 面板里有没有独立图层。
- 加
backface-visibility: hidden是最稳妥的触发方式,不改变视觉,还能绕过某些 WebView 的亚像素 bug -
will-change: transform要动态控制:JS 动画开始前设element.style.willChange = 'transform',结束后立刻设回'auto',长期挂着会吃 GPU 内存 - 别混用
translateZ(0)和will-change,两者目的重复,反而可能干扰浏览器判断
真正难的不是写对一行 transition,而是让浏览器在每一帧都清楚“从哪来、到哪去、怎么走”。高频交互下,任何隐式依赖(比如指望 transitionend 或默认起始值)都会在第 3 次 hover 时暴露出来。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











