banner
sora

sora

编程心得 & 人文感悟

The Tragedy Caused by a Misaligned Scroll

P.S. It has been 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/

Background#

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, specify the container and child elements in the wheel.
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 suddenly became misaligned!
Could this be a mistake in the styles?

Process#

1. Even masters make mistakes?#

Checked F12 and confirmed that better-scroll uses translateZ to implement 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!
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 still be the same next time scrolling.
So I set a breakpoint in the Sources panel to debug scrollTo.

2. Something is not quite right from the beginning#

Noticed that in the resetPosition method of better-scroll, 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 [this.minScrollPos, this.maxScrollPos].
this.maxScrollPos is assigned in the computeBoundary function.
Debugging shows that maxScrollPos is also a decimal.
But according to the above algorithm, it should be an integer, right?

However, in our case, when scrolling on fixed-height child items in the container using wheel, 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.
You can see that the algorithm is relatively simple, it calculates the height of the child item based on the calculation of containerheightnumberofchilditems\frac{container height}{number of child items}, and calculates maxScrollPos based on this.
This algorithm can work correctly if the default heights of the child items are the same.
If there is an error, it means that the heights of the child items are not the same.
So where is the problem?

3. Now, it's time for imagination#

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 img tags.
I wrapped a 250x250 image with an extra <div>.
The result is like this:

image

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 and I got excited for nothing.

So where does this mysterious number come from?

4. The wonders of small fonts#

By adjusting the line-height, it can be observed that this value changes with the line-height.
If I set the line-height to 10px here, the mysterious extra height disappears.
We know that line-height is related to the height of the font. There is a distance from the baseline to the line-height between characters and the bottom of the element.
line-height creates gaps between multiple lines of text. Could this height be the spacing descent left for "multiple lines of text"?

image

Unveiling the mystery of baseline & line-height & vertical-align

If the container is modified to display: flex; or display: grid;,
we can see that this mysterious spacing disappears.

image

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 laid out with display: inline;.
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 done a comparison of the baseline coefficient of Google Fonts: Google Fonts Baseline Coefficient Comparison

image

Conclusion#

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 :)

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.