You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何累积带`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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 16:39:36