CSS 折叠引发的 scrollHeight 异常 —— 一次 Blink 引擎的诡异 Bug

6 阅读2分钟

CSS 折叠引发的 scrollHeight 异常 —— 一次 Blink 引擎的诡异 Bug

现象描述

外层是一个滚动容器,用于渲染消息列表。
列表中有一个答案消息组件,它分为两部分:

  • 思考步骤(可折叠)
  • 内容

思考步骤部分支持折叠。在折叠状态下,会给它加上 CSS:

.thinking-content-collapse {
    max-height: 0;
    opacity: 0;
    overflow: hidden;
}

.thinking-content {
    transition: all .3s ease;
}

之所以不使用 display: none,是因为需要在展开 / 折叠时呈现不透明度的过渡动画。

在普通情况下,一切正常:

  • 折叠 / 展开能正常触发
  • 滚动容器的 scrollHeight 会根据答案消息组件的高度动态变化

但是!
当渲染一个超长的答案消息组件(思考步骤高度约 18000px,内容高度约 2000px)时,在折叠状态下,滚动容器底部会出现一大片空白,而且此时滚动容器的 scrollHeight 诡异地变成了约 12000(不确定这个数字是怎么来的)。


排查过程与原因

排查了很久,最终在 Claude Code 的帮助下,得到解释:

这是 Blink 引擎(Chrome / Edge)的一个边界 Bug:
当元素同时满足 max-height: 0 + overflow: hidden + opacity: 0 时,scrollHeight 的计算路径没有正确应用 max-height 约束,导致内部内容高度“泄漏”到滚动容器的 scrollHeight 中。


解决方案

Claude 给出两种可行方案,实测都能解决问题:

  1. .thinking-content 增加 contain: layout
    作用:隔离元素内外的布局计算。
    缺点:存在浏览器版本兼容性问题。

  2. .thinking-content-collapse 增加 transform: scaleY(0)
    作用:强制创建合成层,使元素走独立的布局计算路径。 缺点:需要对应修改下transition动画的值,否则transform也会产生动画

我们最终采用了方案 2 来修复问题。


疑问

虽然问题解决了,但底层触发机制和原理仍不太清楚。
如果有大佬了解其中的原理,欢迎留言交流,非常感谢!