P.S. 发现已经有人研究过这个问题了
把链接放在这边吧
https://www.zhangxinxu.com/wordpress/2015/08/css-deep-understand-vertical-align-and-line-height/
背景#
这个问题是在使用大佬的 better-scroll 库实现类似 iPhone 的选择日期效果时发现的
在创建时指定 wheel 里面的容器和子元素
子元素大部分都是<p>
,只有一个是<img>
然后神奇的事情发生了,原本好好的滚动到元素突然不对齐了!
这难道是样式写错了吗?
过程#
1. 大佬也有出错的时候?#
去看了 F12,确认 better-scroll 是使用translateZ
来实现的
子元素的高度都是整数,但滚动到最后,translateZ
指定的值居然是小数,这肯定有问题!
手动在 F12 修改translateZ
的值为预期的整数,问题解决了
但这样肯定不行,下次滚动还是老样子
于是在 Sources 面板打断点调试scrollTo
2. 本身就不太对劲#
注意到在 better-scroll 的resetPosition
方法中,滑动到底部后反弹是有一个最低 y 值限制的
// 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 上滚动,实际的范围是在下面算的
// 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 的高度,据此计算maxScrollPos
这种算法能够正确,是建立在默认子 item 的高度都相同的基础上的
它出了误差,说明子 item 的高度并不相同
问题出在哪里呢?
3. 现在是,幻想时间#
注意到给图片类型的子 item 设置高度时,实际的高度与指定的高度是不一致的
就算我在style
属性中手动指定,也无济于事
难道是浏览器的问题?
我找到了 mozilla 基金会官网关于 img 标签的文档
在 250x250 的图片外手动套了一层<div>
结果是这样的:
高度多出了神秘的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 系数对比
结语#
数字世界每个像素背后,都是无数的细节
虽然算不上什么新发现,也没有为社区贡献新的知识,但是解决问题的过程很开心
有这点就够了 :)