从一次滚动不对齐引发的惨案

P.S. 发现已经有人研究过这个问题了
把链接放在这边吧
https://www.zhangxinxu.com/wordpress/2015/08/css-deep-understand-vertical-align-and-line-height/

背景

这个问题是在使用大佬的 better-scroll 库实现类似 iPhone 的选择日期效果时发现的
在创建时指定 wheel 里面的容器和子元素
子元素大部分都是 ，只有一个是

然后神奇的事情发生了，原本好好的滚动到元素突然不对齐了！
这难道是样式写错了吗？

过程

1. 大佬也有出错的时候？

去看了 F12，确认 better-scroll 是使用translateZ 来实现的
子元素的高度都是整数，但滚动到最后，translateZ 指定的值居然是小数，这肯定有问题！
手动在 F12 修改translateZ的值为预期的整数，问题解决了
但这样肯定不行，下次滚动还是老样子
于是在 Sources 面板打断点调试scrollTo

2. 本身就不太对劲

注意到在 better-scroll 的resetPosition方法中，滑动到底部后反弹是有一个最低 y 值限制的

Copy
// https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/core/src/scroller/Scroller.ts#L592
resetPosition(time = 0, easing = ease.bounce) {
const {
position: x,
inBoundary: xInBoundary,
} = this.scrollBehaviorX.checkInBoundary()
const {
position: y,
inBoundary: yInBoundary,
} = this.scrollBehaviorY.checkInBoundary()

if (xInBoundary && yInBoundary) {
return false
}

/* istanbul ignore if */
if (isIOSBadVersion) {
// fix ios 13.4 bouncing
// see it in issues 982
this.reflow()
}
// out of boundary
this.scrollTo(x, y, time, easing)

return true
}

在checkInBoundary中会用到一个this.adjustPosition
后者会把滚动的 x 轴和 y 轴位置约束在[this.minScrollPos, this.maxScrollPos]的范围内
this.maxScrollPos又是在computeBoundary函数中赋值
调试得到maxScrollPos也是一个小数
但按上面的算法，算出来是整数啊？

但是我们的情况是使用wheel在容器内固定高度的子 item 上滚动，实际的范围是在下面算的

Copy
// https://github.com/ustbhuangyi/better-scroll/blob/f87fbd161d4bebb1d1e90fc1675db1b0f90174d0/packages/wheel/src/index.ts#L189
scrollBehaviorY.hooks.on(
scrollBehaviorY.hooks.eventTypes.computeBoundary,
(boundary: Boundary) => {
this.items = this.scroll.scroller.content.children
this.checkWheelAllDisabled()

this.itemHeight =
this.items.length > 0
? scrollBehaviorY.contentSize / this.items.length
: 0

boundary.maxScrollPos = -this.itemHeight * (this.items.length - 1)
boundary.minScrollPos = 0
}
)

这里Wheel对象（迫真 “轮子”）监听 computeBoundary事件，计算minScrollPos和maxScrollPos
可以看到算法比较简单粗暴，就是通过计算 容器的高度子 item 的个数\frac{容器的高度}{子item的个数}子item的个数容器的高度​ 得到子 item 的高度，据此计算maxScrollPos
这种算法能够正确，是建立在默认子 item 的高度都相同的基础上的
它出了误差，说明子 item 的高度并不相同
问题出在哪里呢？

3. 现在是，幻想时间

注意到给图片类型的子 item 设置高度时，实际的高度与指定的高度是不一致的
就算我在style属性中手动指定，也无济于事
难道是浏览器的问题？

我找到了 mozilla 基金会官网关于 img 标签的文档
在 250x250 的图片外手动套了一层
结果是这样的：

高度多出了神秘的5.61px，难道可以给浏览器提 PR 了？
但是找了一圈发现，其他浏览器类似，结果只能证明这是我的少见多怪，白高兴一场

那么这神秘的数字是从哪里来的呢？

4. 小小字体真奇妙

调整line-height可以发现，这个数值会随着line-height变化
在这里我如果把line-height设置为10px，神秘的多余高度就消失了
我们知道line-height与字体高度有关，在字符和元素底部之间会有一个baseline到line-height的距离
line-height是在多行文本之间留出差距，这个高度是不是就是给 “多行文本” 留出的间隔descent呢？

揭开 baseline & line-height & vertical-align 的面纱

将容器修改为display: flex; 或者 display: grid;
我们看到这个神秘的间隔消失了

到此，答案已经基本水落石出了

5. 大师，我悟了

这个多余的间隔，正是图片在 display: inline； 布局时为多行文本留出的
间隔的数字在各个浏览器上不同，可能和 javascript 的计算精度有关，也可能和字体的不同有关

有位大佬专门做了Google 字体的 baseline 系数对比

结语

数字世界每个像素背后，都是无数的细节
虽然算不上什么新发现，也没有为社区贡献新的知识，但是解决问题的过程很开心
有这点就够了 :)