Open xinglie opened 2 years ago
假设有2个div,A和B,它们的clientHeight相同,scrollHeight也相同,如何在滚动A的时候,B也同步滚动?即A和B的滚动条位置相同?
2
div
A
B
clientHeight
scrollHeight
这个问题看上去很简单,我们只要监听A的scroll事件,在这个事件里面设置B的scrollLeft或scrollTop和A相同即可
scroll
scrollLeft
scrollTop
A.addEventListener('scroll',() => { B.scrollTop = A.scrollTop; },{ passive:true });
事实上这个方案虽然简单,但在遇到复杂的页面时:有动画、滚动内容很多等情况下,浏览器已经开始掉帧,那么scroll事件的触发就会被延迟,表现为A已经滚走了一部分内容,而B还在原来的位置上,A和B的滚动并不同步。
这个问题在移动端表现更为明显,因为scroll事件是在滚动结束才触发,许多依赖scroll事件做的虚拟列表都会有该问题,比如这个:https://stackoverflow.com/questions/62489980/virtual-scroll-shows-white-spaces-on-mobile-devices-with-fast-kinetic-scrolling
那么我们该如何解决这个问题,让多个滚动容器同步的显示相应的区域呢?即使卡顿、掉帧的情况下也依然是同步的?
我从vscode中找到了方案
vscode
vscode的输入区域和滚动条完全是自己做的,这样何时滚动、滚动多少完全是自己控制的,在这种情况下,同步多个滚动容器且保持一致是完全可行的。
比如这里:https://github.com/microsoft/vscode/blob/main/src/vs/base/browser/mouseEvent.ts 进行了鼠标事件的封装,尤其是wheel事件的处理
wheel
在这里 https://github.com/microsoft/vscode/blob/main/src/vs/base/browser/ui/scrollbar/scrollableElement.ts#L360 进行了滚动处理。
只不过vscode对滚动区域和滚动条都是自己绘画控制的,难道我们也自己去做滚动条吗?
事实上我们只需要响应wheel事件,阻止默认行为即可,而移动端则需要响应touchstart等事件,同样的阻止默认行为,这样我们带有滚动条的容器就不会滚动了,除非拖动滚动条,而我的测试来看,如果是拖动滚动条滚动的话,是没出现不同步的情况的。
touchstart
vscode处理touch事件可参考这里:https://github.com/microsoft/vscode/blob/main/src/vs/base/browser/touch.ts
touch
所以我们只需要阻止默认的滚动行为,转而变成我们自己控制滚动即可。
A.addEventListener('scroll', () => { B.scrollTop = A.scrollTop; }, { passive: true }); A.addEventListener('wheel', (e: WheelEvent) => { e.preventDefault(); let { deltaMode, deltaY, DOM_DELTA_PIXEL } = e; if (deltaMode != DOM_DELTA_PIXEL) { deltaY = deltaY > 0 ? 1 : -1 } A.scrollTop += deltaY * 2; }, { passive: false })
假设有
2
个div
,A
和B
,它们的clientHeight
相同,scrollHeight
也相同,如何在滚动A
的时候,B
也同步滚动?即A
和B
的滚动条位置相同?这个问题看上去很简单,我们只要监听
A
的scroll
事件,在这个事件里面设置B
的scrollLeft
或scrollTop
和A
相同即可事实上这个方案虽然简单,但在遇到复杂的页面时:有动画、滚动内容很多等情况下,浏览器已经开始掉帧,那么
scroll
事件的触发就会被延迟,表现为A
已经滚走了一部分内容,而B
还在原来的位置上,A
和B
的滚动并不同步。这个问题在移动端表现更为明显,因为
scroll
事件是在滚动结束才触发,许多依赖scroll
事件做的虚拟列表都会有该问题,比如这个:https://stackoverflow.com/questions/62489980/virtual-scroll-shows-white-spaces-on-mobile-devices-with-fast-kinetic-scrolling那么我们该如何解决这个问题,让多个滚动容器同步的显示相应的区域呢?即使卡顿、掉帧的情况下也依然是同步的?
我从
vscode
中找到了方案vscode
的输入区域和滚动条完全是自己做的,这样何时滚动、滚动多少完全是自己控制的,在这种情况下,同步多个滚动容器且保持一致是完全可行的。比如这里:https://github.com/microsoft/vscode/blob/main/src/vs/base/browser/mouseEvent.ts 进行了鼠标事件的封装,尤其是
wheel
事件的处理在这里 https://github.com/microsoft/vscode/blob/main/src/vs/base/browser/ui/scrollbar/scrollableElement.ts#L360 进行了滚动处理。
只不过
vscode
对滚动区域和滚动条都是自己绘画控制的,难道我们也自己去做滚动条吗?事实上我们只需要响应
wheel
事件,阻止默认行为即可,而移动端则需要响应touchstart
等事件,同样的阻止默认行为,这样我们带有滚动条的容器就不会滚动了,除非拖动滚动条,而我的测试来看,如果是拖动滚动条滚动的话,是没出现不同步的情况的。vscode
处理touch
事件可参考这里:https://github.com/microsoft/vscode/blob/main/src/vs/base/browser/touch.ts所以我们只需要阻止默认的滚动行为,转而变成我们自己控制滚动即可。