如何改进程序消除盒子抖动并替代setInterval实现平滑移动?
解决按钮控制盒子位移抖动问题并替换setInterval
问题分析
你的代码出现抖动有几个核心原因:
- 每次按下/松开按钮都创建新的
setInterval,但从未清除旧的,导致多个定时器同时修改位置,相互干扰。 setInterval的执行时机不与浏览器渲染同步,容易出现丢帧或重复执行。- 同时使用
top/left和transform: translate(-50%, -50%),两种定位方式的计算逻辑冲突,导致位置偏移计算混乱。
改进方案
- 用
requestAnimationFrame替代setInterval:它会和浏览器的刷新频率同步,确保动画平滑,且不会出现定时器堆积的问题。 - 维护单一动画循环+状态控制:通过变量记录当前的移动方向(x/y轴的正负增量),而不是每次触发事件就创建新的动画实例。
- 统一使用
transform控制位置:transform的性能远优于直接修改top/left,不会触发频繁的重排,同时避免和初始的translate冲突,改用translate3d来利用GPU加速。 - 清除动画帧:在停止移动时取消当前的动画请求,避免无效执行。
修改后的完整代码
HTML
<div class="mdiv"> <button class="change-color">Color</button> <button class="move-x">Position x+</button> <button class="move-y">Position y+</button> <button class="move-xp">Position x-</button> <button class="move-ym">Position y-</button> <button class="sizeplus" onclick="sizep()">Increase size</button> <button class="sizeminus" onclick="sizem()">Decrease size</button> </div> <div class="box"></div>
CSS
.mdiv { background-color: blanchedalmond; padding: 10px; border-radius: 20px; width: fit-content; margin: 15px; } body { background-color: black; padding: 0; margin: 0; } .box { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); height: 20px; width: 20px; background-color: orangered; border-radius: 4px; /* 添加GPU加速提示 */ will-change: transform; } .move-x, .move-y, .move-xp, .move-ym, .sizeplus, .sizeminus, .change-color { color: black; background-color: grey; border: none; border-radius: 15px; margin: 10px; padding: 15px; display: inline-block; cursor: pointer; }
JavaScript
const pluss = document.querySelector(".box"); const moveY = document.querySelector(".move-y"); const moveX = document.querySelector(".move-x"); const movexP = document.querySelector(".move-xp"); const moveyM = document.querySelector(".move-ym"); const ccol = document.querySelector(".change-color"); // 记录位置增量,0表示停止移动 let deltaX = 0; let deltaY = 0; // 记录当前动画帧ID,用于取消动画 let animationId = null; let size = 20; // 颜色切换逻辑不变 ccol.addEventListener("click", function() { pluss.style.backgroundColor = `hsl(${Math.random() * 360}, 90%, 70%)`; }); // 绑定移动控制事件 moveX.addEventListener("mousedown", () => deltaX = 2); moveX.addEventListener("mouseup", () => deltaX = 0); moveX.addEventListener("mouseleave", () => deltaX = 0); // 防止鼠标移出按钮后继续移动 movexP.addEventListener("mousedown", () => deltaX = -2); movexP.addEventListener("mouseup", () => deltaX = 0); movexP.addEventListener("mouseleave", () => deltaX = 0); moveY.addEventListener("mousedown", () => deltaY = 2); moveY.addEventListener("mouseup", () => deltaY = 0); moveY.addEventListener("mouseleave", () => deltaY = 0); moveyM.addEventListener("mousedown", () => deltaY = -2); moveyM.addEventListener("mouseup", () => deltaY = 0); moveyM.addEventListener("mouseleave", () => deltaY = 0); // 核心动画循环 function animate() { // 如果没有位移需求,直接结束当前帧 if (deltaX === 0 && deltaY === 0) { animationId = null; return; } // 获取当前transform的位置 const computedStyle = window.getComputedStyle(pluss); const transformMatrix = new DOMMatrix(computedStyle.transform); // 更新位置 transformMatrix.e += deltaX; transformMatrix.f += deltaY; // 设置新的transform pluss.style.transform = `translate(${transformMatrix.e}px, ${transformMatrix.f}px)`; // 继续请求下一帧 animationId = requestAnimationFrame(animate); } // 监听位移变化,启动动画 function checkMovement() { if ((deltaX !== 0 || deltaY !== 0) && !animationId) { animationId = requestAnimationFrame(animate); } } // 为所有移动按钮添加状态变化监听 [moveX, movexP, moveY, moveyM].forEach(btn => { btn.addEventListener("mousedown", checkMovement); btn.addEventListener("mouseup", checkMovement); btn.addEventListener("mouseleave", checkMovement); }); // 尺寸控制逻辑 function sizep() { size += 20; pluss.style.height = `${size}px`; pluss.style.width = `${size}px`; } function sizem() { if (size > 20) { // 防止尺寸小于初始值 size -= 20; pluss.style.height = `${size}px`; pluss.style.width = `${size}px`; } }
关键改进点说明
- 状态驱动动画:通过
deltaX和deltaY控制移动方向和速度,只有当有位移需求时才启动动画循环。 requestAnimationFrame:完全同步浏览器渲染节奏,避免定时器的精度问题,动画更平滑。- 统一
transform操作:通过解析当前transform矩阵来更新位置,避免和初始定位冲突,同时利用GPU加速减少重绘。 - 添加
mouseleave事件:防止鼠标移出按钮后盒子继续移动的问题。
内容的提问来源于stack exchange,提问作者Szymon Howorus
相关产品推荐
相关产品推荐

