尝试为container.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); } } } });
问题原因
动画逻辑与实际scrollLeft脱节:
代码中nu_value是基于时间计算的独立进度值(从0到600),每次用nu_value - prev_value的增量修改scrollLeft,但这个增量完全不考虑scrollLeft的实际当前值。当浏览器对scrollLeft做精度修正(比如取整、设备像素比对齐)时,实际scrollLeft会和计算进度脱节,导致最终总滚动量不足600px。时间计算与进度处理不严谨:
使用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

