由 transform 引发的 bug

在 vue 项目的某页面用 better-scroll 滚动条组件的过程中发现:刷新当前页面滚动条显示正常,从其他页面切换过来显示异常(有滚动条但显示异常)。

第一反应是被别的页面样式影响到了,排查没问题。

接着分析 better-scroll 可能出现滚动条异常的原因:1.层级关系出错,2.计算高度出错。

  1. 层级关系

外层 wrapper 层,固定高度,且设置overflow:hidden。内层第一个子元素content层,高度大于 wrapper 显示滚动条。页面出现滚动条,不是此问题。

  1. 计算高度出错

打印BS对象,查看hasVerticalScrollscrollerHeightwrapperHeight值。如果是因为未出现滚动条,则一般scrollerHeight小于wrapperHeight。可能是因为在数据未渲染成功时加载滚动条导致。

查看内置属性,发现外层高度wrapperHeight在切换页面时会出现塌陷。获取数据后用 setTimeout 延迟几秒就正常,以为是常见的数据未渲染成功时就加载导致的,分析这个过程又持续了好久无果。又发现设置 setTimeout 延迟 3 秒就正常,延迟时间小于 1 秒就失败,限制网速也可以正常显示。这就很诡异了,又不是数据渲染的问题,但延迟时间又有效果。折腾好久还是没找到问题。
(其实当时重点关注不应该是这里,应该主要分析切换页面高度异常这个问题)

又一层层分析打印各个元素及其高度,发现有元素打印出来指向不到页面选中的状态,也就是说,虽然控制台打印出来了,但是元素不在这个页面,这个问题很重要了,为什么会出现这种情况呢,难道有动画?往上层找发现页面的外层框架(d2-admin)在某个页面(路由)切换的时候会有一个0.5秒的动画,同时也发现此页面最外层 div 高度是 0 (flex-grow 子元素设置百分比无效)。外层框架是 flex布局。而且只有在有 transform 属性的时候才会出现这个问题。

用一个简单的例子还原一下。
demo

延长动画的时间,并将 page 设置成固定高度,比较明显的看到在动画结束前内层元素 page-inner 都没有撑开到正常的高度。当去掉动画或者将外层 page 设置为position:absolute,都可以解决问题。

那么问题来了,transform 动画为什么会导致内层元素高度失效?设置为绝对定位为什么就不受动画的影响?

transform会提升元素的垂直地位,使元素表现出position:relative的特性,导致堆叠上下文(Stacking Context)和包含块(Containing Block)的创建,其后代元素(含有fixed属性或absolute属性)会以该父元素作为包含块。而当父元素page元素为position:relative的时候,子元素 page-inner 会根据父元素来定位,所以导致高度为 0 ,去掉动画后,也就没有了transform属性。就根据再上一级来定位。

至此,解决了花费时久的坑。