如何累积带`behavior: "smooth"`的连续`scrollBy`调用以实现流畅滚动?
如何累积带
behavior: "smooth"的连续scrollBy调用以实现流畅滚动? 嘿,我来帮你梳理这个问题。首先直接回答你的第一个问题:浏览器原生并不支持累积连续的smooth模式scrollBy调用——每次触发新的scrollBy({behavior: "smooth"}),浏览器都会立即中断正在进行的平滑滚动动画,优先处理最新的请求,这是浏览器的默认行为,目的是响应用户最新的输入,但确实会导致你遇到的滚动不连贯、速度不达预期的问题。
那针对第二个问题,我们可以用一种“滚动队列”的思路来解决,既不用手动实现平滑动画,又能避免中断问题,核心是等上一次平滑滚动结束后,再处理累积的滚动请求。具体做法如下:
解决方案:滚动队列 + scrollend事件
我们可以维护一个队列来存储用户的滚动增量,当一次平滑滚动完成后(通过scrollend事件判断),再从队列中取出累积的总增量执行下一次平滑滚动。这样就能让浏览器完整完成每一次动画,同时不会丢失用户的滚轮输入。
步骤1:添加状态变量
首先定义几个全局变量来跟踪滚动状态和队列:
// 存储待处理的滚动请求队列 const scrollQueue = []; // 标记当前是否有平滑滚动动画在进行 let isScrolling = false;
步骤2:修改滚轮事件处理函数
在onWheel中,不再直接触发scrollBy,而是把滚动增量加入队列,然后判断如果当前没有动画在执行,就启动队列处理:
function onWheel(e) { if (!e.altKey || e.deltaMode !== WheelEvent.DOM_DELTA_PIXEL) return; e.preventDefault(); e.stopImmediatePropagation(); const { deltaY } = e; const amplified = deltaY * fastScrollSensitivity; const [vertical, plus] = [!e.shiftKey, e.deltaY > 0]; const el = findScrollableElement(e, vertical, plus); Object.assign(lastScroll, { time: e.timeStamp, el, vertical, plus }); // 将当前滚动请求加入队列,记录目标元素和滚动增量 scrollQueue.push({ el, top: e.shiftKey ? 0 : amplified, left: e.shiftKey ? amplified : 0 }); // 如果当前没有正在进行的滚动,启动队列处理 if (!isScrolling) { processScrollQueue(); } }
步骤3:实现队列处理函数
这个函数负责合并队列中的滚动请求,并触发平滑滚动,同时监听动画结束事件来处理下一批请求:
function processScrollQueue() { // 队列空了就标记结束状态 if (scrollQueue.length === 0) { isScrolling = false; return; } isScrolling = true; // 合并队列中针对同一个元素的滚动增量(如果是不同元素可以分开处理) const firstReq = scrollQueue.shift(); const combinedReq = scrollQueue.reduce((acc, curr) => { if (acc.el === curr.el) { return { el: acc.el, top: acc.top + curr.top, left: acc.left + curr.left }; } // 如果遇到不同元素,暂时先保留后续请求,这里可以根据需求调整逻辑 scrollQueue.unshift(curr); return acc; }, firstReq); // 清空已合并的请求 scrollQueue.length = 0; // 触发浏览器原生平滑滚动 combinedReq.el.scrollBy({ top: combinedReq.top, left: combinedReq.left, behavior: "smooth" }); // 监听滚动结束事件,处理下一批队列请求 const handleScrollEnd = () => { combinedReq.el.removeEventListener('scrollend', handleScrollEnd); processScrollQueue(); }; combinedReq.el.addEventListener('scrollend', handleScrollEnd); }
补充说明
scrollend是现代浏览器支持的事件,专门用于监听滚动动画结束,兼容性很好(除了一些旧版浏览器)。如果需要兼容更老的环境,可以用“监听scroll事件+时间差判断”的方式来模拟动画结束,但scrollend是最优选择。- 这个方案的核心是把用户的连续滚动请求累积起来,等上一次平滑动画完成后再一次性执行,既保留了浏览器原生的平滑动画效果,又避免了中断问题,完全符合你的需求。
备注:内容来源于stack exchange,提问作者PRO
相关产品推荐
相关产品推荐

