不固定高度的多节点同步滚动

Author Avatar
jiandandkl 4月 10, 2023
本文总阅读量
  • 在其它设备中阅读本文章

最近有这么一个功能,用户编辑的时候实现编辑区和预览区同步滚动,但是两边内容的高度是不一样的。

算出比例同步滚动

大体思路是计算每次滚动的距离与该节点整体高度的比例,另一个节点滚动的距离为该比例 * 当前节点的高度。


function syncScroller () {
    let nodes = Array.prototype.filter.call(arguments, item => item instanceof HTMLElement)
    let max = nodes.length
    if (!max || max === 1) return
    let sign = 0;

    function event () {
        if (!sign) {
            sign = max - 1;
            let top = this.scrollTop
            const rate = top / this.scrollHeight
            let left = this.scrollLeft
            for (node of nodes) {
                if (node == this) continue;
                node.scrollTo(left, node.scrollHeight * rate);
            }
        } else
        -- sign;
    }

    nodes.forEach((ele, index) => {
        ele.addEventListener('scroll', event);
    });

    return () => {
        nodes.forEach((ele, index) => {
            ele.removeEventListener('scroll', event);
        });
    }
}

但效果却不尽人意,因为有可能编辑区块高度很小,预览区块却很大(如占满整屏),就会造成不同步的现象。

使用 scrollview 实现滚动

只能转变思路了,看到有个 scrollIntoView 的方法,可以将元素滚动至可视区某个位置。那就可以监听滚动区域是第几个元素,将另一边相同顺序的元素滚动至可视区即可。

获取当前滚动元素的 index
    const container = this.querySelector('#scrollBlocks')
    let currentIndex = 0
    if (container?.children) {
        [...container?.children].forEach((sectionRef, index) => {
            const { offsetTop, clientHeight } = sectionRef
            if (this.scrollTop >= offsetTop - clientHeight * (1 / 2)) {
            currentIndex = index
            }
        })
    }

以上 function 来自 chatgpt

预览区元素滚动
    node.children?.[currentIndex === 0 ? 0 : currentIndex + 1]?.scrollIntoView()

效果图:

完整 code

这个方法的问题在于不是实时滚动,只会在编辑区滚动到某个元素时,预览区滚动一次,不过也满足当前的需求了。

参考:

原生 JS 解决多节点滚动同步联动