如何区分手动滚动与通过el.scrollIntoView()触发的程序化滚动
区分手动滚动与
scrollIntoView触发的程序化滚动 你的问题核心是要避免scrollIntoView触发的滚动事件打断自动滚动逻辑,关键在于设置一个程序化滚动的标记,让scroll监听器能精准区分滚动来源。下面是具体的可落地解决方案:
核心思路
在调用el.scrollIntoView()前设置一个标记,告知scroll监听器“这次滚动是代码触发的,不要执行暂停逻辑”;同时因为behavior: 'smooth'是异步完成的,需要在滚动完全结束后重置标记,确保后续手动滚动能正常触发暂停逻辑。
具体代码修改步骤
1. 在父组件中添加程序化滚动标记
用useRef存储标记状态,避免组件渲染时重置标记值:
const [autoScrollEnabled, setAutoScrollEnabled] = useState(true); const autoScrollDelay: any = useRef(null); // 添加ref存储程序化滚动标记 const isProgrammaticScroll = useRef(false); // 修改钩子调用,传入设置标记的回调函数 useAutoScroll(autoScrollEnabled, (value) => { isProgrammaticScroll.current = value; });
2. 修改useAutoScroll钩子,加入标记控制逻辑
让钩子能设置和重置标记,同时处理平滑滚动的结束事件:
export const useAutoScroll = (enabled = true, setIsProgrammaticScroll) => { const currentTime = useSelector(state => state.timeline.currentTime); const isPlaying = useSelector(state => state.timeline.isPlaying); const shouldAutoScroll = currentTime && enabled && isPlaying; // 存储重置标记的函数,避免重复绑定事件 const resetMarkRef = useRef(() => {}); useEffect(() => { if (shouldAutoScroll) { const currElUuid = calculateUuid(); const el = document.querySelector(`[data-uuid="${currElUuid}"]`); if (el) { // 先清除之前的监听器和超时,防止多次触发导致的冲突 el.removeEventListener('transitionend', resetMarkRef.current); clearTimeout(resetMarkRef.current.timeoutId); // 设置程序化滚动标记 setIsProgrammaticScroll(true); el.scrollIntoView({ behavior: 'smooth', block: 'end' }); // 定义重置标记的函数 const resetMark = () => { setIsProgrammaticScroll(false); el.removeEventListener('transitionend', resetMark); }; resetMarkRef.current = resetMark; // 监听平滑滚动结束(底层依赖CSS transition) el.addEventListener('transitionend', resetMark); // 保险超时:防止transitionend未触发(比如元素已经在可视区域,scrollIntoView未执行滚动) resetMark.timeoutId = setTimeout(resetMark, 1000); } } }, [currentTime, enabled, isPlaying, subs, lastHighlightedUuid, setIsProgrammaticScroll]); };
3. 修改scroll监听器,跳过程序化滚动的暂停逻辑
在onScroll回调中检查标记,只有手动滚动时才执行pauseAutoScroll:
const onScroll = useCallback( event => { // 仅当是手动滚动时,才触发暂停自动滚动 if (isPlaying && !isProgrammaticScroll.current) { pauseAutoScroll(); } }, [isPlaying], );
方案优势
- 覆盖全场景:通过标记区分滚动来源,完美解决
wheel事件无法覆盖拖动滚动条的问题; - 处理异步滚动:监听
transitionend事件确保平滑滚动完全结束后才重置标记,避免中途的scroll事件误判; - 边界情况兜底:添加超时重置逻辑,防止元素已在可视区域时
transitionend未触发的异常场景。
内容的提问来源于stack exchange,提问作者anjapopovic
相关产品推荐
相关产品推荐

