banner
sora

sora

编程心得 & 人文感悟

從一次滾動不對齊引發的慘案

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

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.

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"?

image

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.

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 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.

image

结语#

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

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。