Failure investigation and replacement scheme of scrollIntoView

Posted by Formula on Thu, 03 Feb 2022 10:27:10 +0100

Problem background

Today, we need to do a function of clicking icon to slide to the article comment area, using scrollIntoView. It is found that the mobile terminal occasionally fails.

The code is as follows:

 commentRef.current.scrollIntoView({
     behavior: 'smooth',
 });

analysis

Think about whether 1 is caused by a browser bug

according to This blog post The description is that if the native event is monitored during the sliding process, the continuous execution of the event will be blocked.

Therefore, by replacing with scollTo, it is found that the sliding is improved, but there is still the problem of inaccurate positioning.

according to This answer We can use requestAnimationFrame to dynamically implement scrollIntoView

const requestAnimationFrame = window.requestAnimationFrame ||
          window.mozRequestAnimationFrame ||
          window.webkitRequestAnimationFrame ||
          window.msRequestAnimationFrame;

const step = () => {
    const footer = footerCommentRef.current;
    const footerStaticTop = footer.offsetTop;
    const footterBottom = footer.getBoundingClientRect().bottom;

    if (container.scrollTop >= (footerStaticTop)
    || ((footterBottom + bottomPaddingHeight) < (window.innerHeight)) ) {
        return;
    }
    const restScrollValue = footerStaticTop - container.scrollTop;
    const scrollValue = restScrollValue > scrollLen ? scrollLen : restScrollValue;

    container.scrollBy(
        0,
        scrollValue,
    );

    requestAnimationFrame(step);

};

requestAnimationFrame(step);

Think about whether 2 is a compatibility issue

I tried all the above methods and found that there was still a problem, so I thought about whether there was a problem with compatibility.
Start with scrollIntoView again and take here Install Smoothscroll Polyfill.

A promise is reported after installing Smoothscroll Polyfill Then is not a function error, successfully hung up the qa environment.

According to caniuse, the compatibility of these three methods in Safari is not good,
At the end of the road, the problem has not been solved, so reflect on whether it is caused by other problems.

Think about 3 whether it is caused by other reasons

After investigation, it was finally found that the picture did not occupy / occupied inaccurate space in the loading process of landing page, which led to the problem of inaccurate scrolling. Therefore, improve the above code.

First, all landing page images need to be monitored to see whether they have been loaded.

const isAllImageLoaded: Promise<any> = function(
    container: HTMLElement
) {
    const images: HTMLImageElement[] = container.querySelectorAll('img');
    if (images.length === 0) {
        return true;
    }
    const promiseImage = [];
    for (let item of images) {
        promiseImage.push(new Promise(resolve => {
            item.loaded = function() {
                resolve();
            };
        }));
    }
    return Promise.all(promiseImage);
};

In addition to monitoring the picture loading status, you should also pay attention to the following points:

  • When the user operates the scroll wheel or operates the mobile phone to slide, the scrolling needs to be stopped.
  • Judge the number of scrolls according to the length of the article, rather than the scrolling of a fixed length, so as to avoid the long scrolling time of a long article.
  • Record the position of the last scroll. If the two scroll positions are the same, it means that the scroll cannot be continued and needs to be stopped.
    The final code is as follows:
const CHANGESCROLLEVENT = [
    'wheel',
    'touchmove',
    'touchstart',
];
/**
 *  @note: Scroll to comment area public method
 *  @param target Target dam
 *  @param container container dam
 */
export const scrollIntoComment = async function(
    target: HTMLElement,
    container: HTMLElement
) {
    let isUserScroll = false;

    let requestAnimationFrame = window.requestAnimationFrame ||
          window.mozRequestAnimationFrame ||
          window.webkitRequestAnimationFrame ||
          window.msRequestAnimationFrame;

    CHANGESCROLLEVENT.forEach(item => {
        container.addEventListener(item, () => {
            isUserScroll = true;
        });
    });


    const step = () => {

        const targetTop = target.offsetTop; // This value may change, so put it in it
        const scrollLen = targetTop / 15;
        const curScrollTop = container.scrollTop;
        if (curScrollTop >= targetTop
        || isUserScroll) {
            return;
        }
        const restScrollValue = targetTop - curScrollTop;
        const scrollValue = restScrollValue > scrollLen ? scrollLen : restScrollValue;

        container.scrollBy(
            0,
            scrollValue,
        );

        if (curScrollTop !== container.scrollTop) {
            requestAnimationFrame(step);
        }

    };


    // Scroll first before loading
    requestAnimationFrame(step);

    await isAllImageLoaded(container);

    await requestAnimationFrame(step);
};

more

[stackoverflow]Javascript - window.scroll({ behavior: 'smooth' }) not working in Safari

Topics: Javascript Front-end React html css