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

尝试为container.scrollLeft赋值已知值却得到未知值的问题排查

横向滚动动画scrollLeft值不符的问题分析与修复

问题背景

我创建了一个横向滚动容器:宽度600px的flex容器内包含8个宽度150px的子div,每次点击侧边按钮需要滚动600px(切换一组4个div)。使用requestAnimationFrame实现滚动动画时,发现计算的滚动增量(nu_value - prev_value)赋值给container.scrollLeft后,实际打印的scrollLeft值与预期不符,导致滚动无法达到目标距离(左右滚动都不彻底)。

原始HTML结构:

<body>
    <button id="button_left"> < </button>
    <div id="container">
        <div id="sub1" class="sub">1</div>
        <div id="sub2" class="sub">2</div>
        <div id="sub3" class="sub">3</div>
        <div id="sub4" class="sub">4</div>
        <div id="sub5" class="sub">5</div>
        <div id="sub6" class="sub">6</div>
        <div id="sub7" class="sub">7</div>
        <div id="sub8" class="sub">8</div>
    </div>
    <button id="button_right"> > </button>
</body>

原始核心JS代码(问题相关部分标记为COMMENTED):

let container = document.querySelector("#container");
let button_left = document.querySelector('#button_left');
let button_right = document.querySelector('#button_right');

button_left.addEventListener('click', ()=>{
    if(container.scrollLeft > 0){
        let start, elapsed, progress, prev_value = 0, nu_value, stop = 0;
        requestAnimationFrame(scroll);
        function scroll(){
            if(start === undefined)
            start = new Date().getTime();
            elapsed = new Date().getTime() - start;
            if(stop == 0){
                progress = Math.round((elapsed/900).toFixed(2) * 600); // 900 is time to scroll in ms
                nu_value = Math.min(progress, 600);
                if(nu_value == 600)
                stop = 1;
                console.log(`difference = ${nu_value - prev_value}`);  //COMMENTED
                container.scrollLeft -= nu_value - prev_value;          //COMMENTED
                console.log(`scrollLeft = ${container.scrollLeft}`);   //COMMENTED   
                prev_value = nu_value;
                requestAnimationFrame(scroll);
            }
        }
    }
});
button_right.addEventListener('click', ()=>{
    if(container.scrollLeft >= 0){
        let start, elapsed, progress, prev_value = 0, nu_value, stop = 0;
        requestAnimationFrame(scroll);
        function scroll(){
            if(start === undefined)
            start = new Date().getTime();
            elapsed = new Date().getTime() - start;
            if(stop == 0){
                progress = Math.round((elapsed/900).toFixed(2) * 600); // 900 is time to scroll in ms
                nu_value = Math.min(progress, 600);
                if(nu_value == 600)
                stop = 1;
                console.log(`difference = ${nu_value - prev_value}`);  //COMMENTED
                container.scrollLeft += nu_value - prev_value;          //COMMENTED
                console.log(`scrollLeft = ${container.scrollLeft}`);   //COMMENTED   
                prev_value = nu_value;
                requestAnimationFrame(scroll);
            }
        }
    }
});

问题原因

  1. 动画逻辑与实际scrollLeft脱节:
    代码中nu_value是基于时间计算的独立进度值(从0到600),每次用nu_value - prev_value的增量修改scrollLeft,但这个增量完全不考虑scrollLeft的实际当前值。当浏览器对scrollLeft做精度修正(比如取整、设备像素比对齐)时,实际scrollLeft会和计算进度脱节,导致最终总滚动量不足600px。

  2. 时间计算与进度处理不严谨:
    使用new Date().getTime()计算时间差的精度低于requestAnimationFrame自带的timestamp参数;同时(elapsed/900).toFixed(2)会截断进度值,导致进度跳变,进一步加剧scrollLeft的偏差。

修复方案

核心思路:基于scrollLeft的初始值和目标值,用动画进度直接计算当前应设置的scrollLeft,而非累加增量。这样即使中间有精度修正,动画结束时也会准确到达目标值。

修复后的JS代码:

let container = document.querySelector("#container");
let button_left = document.querySelector('#button_left');
let button_right = document.querySelector('#button_right');

// 左侧滚动按钮逻辑
button_left.addEventListener('click', () => {
    const initialScroll = container.scrollLeft;
    const targetScroll = initialScroll - 600;
    // 防止滚动超出左边界
    if (targetScroll < 0) return;
    
    let start;
    function scroll(timestamp) {
        if (start === undefined) start = timestamp;
        const elapsed = timestamp - start;
        // 计算0-1的动画进度,900ms完成滚动
        const progress = Math.min(elapsed / 900, 1);
        // 线性插值计算当前scrollLeft,可替换为缓动函数优化动画
        const currentScroll = initialScroll - progress * 600;
        container.scrollLeft = currentScroll;
        
        if (progress < 1) {
            requestAnimationFrame(scroll);
        }
    }
    requestAnimationFrame(scroll);
});

// 右侧滚动按钮逻辑
button_right.addEventListener('click', () => {
    const initialScroll = container.scrollLeft;
    const targetScroll = initialScroll + 600;
    // 防止滚动超出右边界(容器总宽度 - 可视宽度)
    if (targetScroll > container.scrollWidth - container.clientWidth) return;
    
    let start;
    function scroll(timestamp) {
        if (start === undefined) start = timestamp;
        const elapsed = timestamp - start;
        const progress = Math.min(elapsed / 900, 1);
        const currentScroll = initialScroll + progress * 600;
        container.scrollLeft = currentScroll;
        
        if (progress < 1) {
            requestAnimationFrame(scroll);
        }
    }
    requestAnimationFrame(scroll);
});

修复要点

  • 使用requestAnimationFrame的timestamp参数计算时间差,精度更高
  • 基于初始scrollLeft和目标值,用0-1的进度直接计算当前scrollLeft,避免增量累加的偏差
  • 增加边界判断,防止滚动超出容器范围
  • 去掉冗余的进度变量,逻辑更简洁清晰

内容的提问来源于stack exchange,提问作者dRIFT sPEED

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 20:58:14