P.S. I found that someone has already researched this issue. Let's put the link here: https://www.zhangxinxu.com/wordpress/2015/08/css-deep-understand-vertical-align-and-line-height/
背景#
This issue was discovered when using the better-scroll library by a master to achieve a date selection effect similar to the iPhone. When creating it, the container and child elements inside the wheel were specified. Most of the child elements are <p>
, with only one being <img>
.
Then something magical happened, the scrolling to the elements that were originally aligned properly suddenly became misaligned! Did I write the styles wrong?
过程#
1. Even masters make mistakes?#
I checked F12 and confirmed that better-scroll uses translateZ
to achieve it. The heights of the child elements are all integers, but when scrolling to the end, the value specified by translateZ
is actually a decimal, which is definitely a problem! I manually modified the value of translateZ
to the expected integer in F12, and the problem was solved. But this is definitely not a solution, it will be the same next time I scroll. So I set a breakpoint in the Sources panel to debug scrollTo
.
2. Something is not quite right#
I noticed in the resetPosition
method of better-scroll that there is a minimum y value limit for bouncing after scrolling to the bottom.
// 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
}
In checkInBoundary
, it uses this.adjustPosition
, which constrains the scrolling x and y positions within the range of [this.minScrollPos, this.maxScrollPos]
. this.maxScrollPos
is assigned in the computeBoundary
function. I debugged and found that maxScrollPos
is also a decimal. But according to the above algorithm, it should be an integer, right?
However, in our case, we are using wheel
to scroll on fixed-height child items within the container, and the actual range is calculated below:
// 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
}
)
Here, the Wheel
object listens for the computeBoundary
event and calculates minScrollPos
and maxScrollPos
. As you can see, the algorithm is quite simple and straightforward. It calculates the height of the child item based on the calculation of and calculates maxScrollPos
based on this. This algorithm can work correctly if the default heights of the child items are the same. The fact that there is an error indicates that the heights of the child items are not the same. So where is the problem?
3. Now, it's time for imagination#
I noticed that when setting the height for the image type child item, the actual height is different from the specified height. Even if I manually specify it in the style
attribute, it doesn't work. Could it be a browser issue?
I found the Mozilla Foundation's documentation on the img tag. I wrapped a 250x250 image with an extra <div>
outside. The result is as follows:
The height is increased by a mysterious 5.61px
. Can I submit a PR to the browser for this? But after searching around, I found that other browsers have similar results, which means that this is just my imagination.
So where does this mysterious number come from?
4. The wonders of small fonts#
By adjusting the line-height
, I found that this value changes. If I set line-height
to 10px
, the mysterious extra height disappears. We know that line-height
is related to the font height, and there is a distance from the baseline
to line-height
between characters and the bottom of the element. line-height
creates spacing between multiple lines of text. Could this height be the descent
left for "multiple lines of text"?
Unveiling the mystery of baseline & line-height & vertical-align
If I change the container to display: flex;
or display: grid;
, I can see that this mysterious spacing disappears.
At this point, the answer is basically clear.
5. Master, I understand#
This extra spacing is precisely the spacing left for "multiple lines of text" when the image is in display: inline;
layout. The value of the spacing is different in various browsers, which may be related to the precision of JavaScript calculations or different fonts.
A master has specifically compared the baseline coefficients of Google Fonts.
结语#
Behind every pixel in the digital world, there are countless details. Although it is not a new discovery and does not contribute new knowledge to the community, I am happy with the process of solving the problem. Having this is enough :)