[前端] 一次动画冲突引发的"卡顿"

RoLingG 前端 2026-07-09

一次动画冲突引发的"卡顿"

在上一篇中,我解决了 height: auto → 0 无法平滑动画的问题,本以为事情就结束了。

结果又发现了一个新问题。

Pool Menu(下拉菜单)处于展开状态时,如果切换到 Summary 模式,整个 Pool Selector 会先"顿一下",然后下面的内容才开始上移。

最开始我以为还是 heightmargin-bottom 动画的问题,甚至尝试把 margin-bottom 也加入 Transition

el.style.transition =
    "height .25s ease-in, opacity .20s ease-out, margin-bottom .25s ease-in";

结果是得到了一些改善,动画不再会有 margin 那一下无过度的卡顿感,整体来和展开前的动画差不多流畅。但实际上认真看还是会有些许不属于这个 Wrapper 整体渐出时会有的卡顿感。


真正的问题

后来重新梳理整个组件的结构,才发现问题并不是出在 Wrapper,而是同一个组件存在两套独立的收起动画

Pool Selector 本身负责整个组件的显示与隐藏:

Wrapper
└── height / opacity / margin-bottom

而它内部的下拉菜单(Pool Menu)也有自己的展开/收起动画:

.pool-menu {
    transition: max-height .3s ease;
}

正常情况下,这两个动画分别负责不同的事情:

  • 点击下拉框时,由 Pool Menu 负责展开和收起;
  • 页面切换时,由 Wrapper 负责整个组件的显示和隐藏。

但当我切换到 Summary 模式时,却无意间让它们同时参与了退出动画。整个过程实际上变成了:

Pool Menu 收起(max-height)    310 → 280 → 250 → ...

Wrapper 收起(height)            68 → 60 → 52 → ...

这两个动画属性有一个共同点,都会触发布局(Layout / Reflow)

这就会导致浏览器的每一帧渲染都会为:

Layout Menu
    ↓
Layout Wrapper
    ↓
  Paint

也就是说,浏览器需要先处理 Pool Menumax-height 变化,再处理 Wrapperheight Collapse。虽然这两个动画并没有真正互相阻塞,但它们都在同一个组件上修改布局,最终给人的视觉感受就是:整个组件像是"停顿了一下",然后才开始整体收起。


解决办法

既然知道了两个动画会有渲染上的布局冲突问题,那我们只需要优化掉一个就好了,但问题需要优化掉哪个呢?

后来我突然意识到一个问题:当 Wrapper 开始 Collapse 时,它本身已经在执行 height → 0,并且整个 Wrapper 还有 overflow: hidden 这个样式。

这意味着,只要 Wrapper 开始收起,里面所有内容都会一起被裁剪掉,包括已经展开的 Pool Menu

换句话说,整个组件已经有了一个退出动画,那么 Pool Menu 再去播放一次:max-height → 0,其实已经没有意义了,它只是重复了一次 "收起" 的动作。

.pool-menu {
    transition: max-height .3s ease;    ←重复
}

于是我直接在收起 Wrapper 之前,先关闭 Pool MenuTransition

const menu = el.querySelector(".pool-menu");

if (menu) {
    menu.style.transition = "none";
    menu.classList.remove("show");
}

然后再执行 WrapperCollapse,这样整个退出过程就变成了:

Wrapper 收起(唯一的退出动画)
        ↓
Pool Menu 被 Wrapper 一起裁剪掉

动画立刻变得流畅了许多,不再有之前那种 "停顿一下" 的感觉。


思考

一个组件可以拥有很多进入动画,例如:

  • 下拉菜单展开
  • 图标旋转
  • 内容淡入

这些动画互相配合,会让组件更有层次感。但是退出动画最好只有一个"拥有者"(Animation Owner)

对于这个组件来说:

  • 用户点击下拉框时,由 Pool Menu 负责展开动画。
  • 页面切换、整个组件隐藏时,则完全交给 Wrapper 负责收起。

不要让两个动画同时争夺同一个组件的退出过程,否则很容易出现动画冲突,最终表现出来就是一种说不出来的 "卡顿感"

PREV
[前端] 为什么 CSS 设置了渐出动画却瞬间消失?一次关于前端动画问题排查记录

评论(0)

发布评论