scrollIntoView 失效调研与替换方案

5,197 阅读2分钟

问题背景

今天需要做一个点击icon滑动到文章评论区的功能,采用了scrollIntoView,发现在移动端偶现失效了。

代码如下:

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

分析

思考1 是否由于浏览器bug导致

这篇博文描述是由于滑动过程中进行了原生事件的监听就会阻断事件继续执行。

因此替换成 scollTo,发现滑动有改善,但是还是有定位不准的问题。

根据这个回答得出可以采用 requestAnimationFrame 来进行动态实现 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);

思考2 是否是兼容性问题

上面的方法都试了一遍,发现还是有问题,因此思考是否是兼容性方面出了问题。 重新从 scrollIntoView 着手,采取这里的兼容方案,安装 smoothscroll-polyfill

安装 smoothscroll-polyfill 后报了一个 promise.then is not a function 的错误,成功把qa环境搞挂了。

查阅 caniuse 得这三个方法在Safari的兼容性并不好, 路子走到头了,问题还是没有解决,因此反思是否其他问题导致的。

思考3 是否是其他原因导致的

调研到最后发现是由于图片在落地页的加载过程中没占位/占位不准确导致了滚动不精确的问题。因此再对上面的代码进行改良。

首先需要对所有的落地页图片进行监听,查看其是否完成了加载。

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);
};

除了需要对图片加载状态进行监听外,还需要注意以下几点:

  • 当用户操作滚轮或者操作手机滑动时需要停止滚动。
  • 根据文章的长度去判断滚动次数而不是固定长度的滚动,避免长文章滚动时间过长。
  • 记录上一次滚动的位置,若两次滚动位置相同,说明已无法继续滚动,需要停止滚动。 最终代码如下:
const CHANGESCROLLEVENT = [
    'wheel',
    'touchmove',
    'touchstart',
];
/**
 *  @note: 滚动到评论区公共方法
 *  @param 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; // 这个值可能会变 所以放里面
        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);
        }

    };


    // 先滚动在加载
    requestAnimationFrame(step);

    await isAllImageLoaded(container);

    await requestAnimationFrame(step);
};

更多

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