功能背景
项目中遇到需要监听当前用户操作的滚动容器的滚动位置,用以获得当前用户正在查看的内容模块,便于制作导航条展示用户当前所查看的模块(类似页内导航功能)。故想要通过实时监听滚动位置,根据判断滚动停止来执行相应事件,但js中并没有监听滚动停止事件,只能通过在滚动的监听事件中增加定时器来判断滚动是否停止。
功能实现方式分析
- 初始时,使用 ahooks 的 useScroll,实时获取滚动位置数据进行操作,实现功能,但使用过程中发现滚动条不断改变位置的同时,scroll的值也在不断更新,查看源码发现在监听滚动条滚动过程中,也在不断进行setState操作,当页面功能复杂,dom节点较多的情况下,可能严重影响性能和体验。
// ahooks 中 useScroll 的源码部分:setPosition 执行的是内部封装的 useRafState, 本质上也是进行了 setState 操作
if (shouldUpdateRef.current(newPosition)) {
setPosition(newPosition);
}
- 后来考虑通过 ahooks 的 useEventListener 监听滚动条滚动事件实现。但考虑到性能问题,需要判断滚动结束后再去获得滚动位置数据,并进行数据的操作。
通过设置监听器
,在不停地触发滚动时,不会执行定时器内的方法,当滚动停止时,定时器前后两次获取的scrollTop
应该是相同的,可以此作为判断依据。
const scrollRef = useRef(null);
// 滚动结束位置信息
const [scrollPosition, setScrollPosition] = useState({top: 0, left: 0});
const timer = useRef(null);
const topValue = useRef(0);
// 滚动事件监听
useEventListener(
'scroll',
() => {
clearTimeout(timer.current)
topValue.current = scrollRef.current.scrollTop;
timer.current = setTimeout(()=>{
if(scrollRef.current.scrollTop === topValue.current) {
const position = {
top: scrollRef.current.scrollTop,
left: scrollRef.current.scrollLeft
};
setScrollPosition(position)
// TODO 执行后续滚动结束的事件
// ...
}
}, 300);
},
{ target: scrollRef },
);
页面布局部分:container 为滚动容器,div1、div2、div3为滚动内容,滚动停止时,将获取当前滚动条的scrollTop, 并以此判断当前停留在哪个div上,从而进行标记,以此实现页面导航功能。
<div className='container' style={{position: 'relative'}} ref={scrollRef}>
<div data-anchorpointid='div1'>div1</div>
<div data-anchorpointid='div2'>div2</div>
<div data-anchorpointid='div3'>div3</div>
</div>