弹跳小球动画仅向右滚动异常的原因及修复咨询
问题原因
问题出在拖拽时速度计算依赖的DOM属性不一致:
ball.getBoundingClientRect().left获取的是小球相对于视口的左侧位置ball.offsetLeft获取的是小球**相对于父元素(body)**的左侧位置- 由于body默认存在8px的margin,这两个值始终存在固定差值,导致每次拖拽后
velocityX = newX - ball.offsetLeft的结果始终为正的固定值(比如8px),所以小球释放后会一直向右移动,直到碰撞视口右侧。
修复方案:用拖拽位置差计算速度
不再依赖DOM的offsetLeft/offsetTop属性计算速度,而是直接记录拖拽过程中每次的位置变化量,彻底避免DOM布局差异带来的错误。
修改后的完整JavaScript代码:
var ball = document.querySelector('.ball'), isDragging = false, isAnimating = false, isAnimationAllowed = true, offsetX, offsetY, velocityX = 0, velocityY = 0, friction = 0.95, gravity = 0.5, floorLevel = 500, animationFrame, lastDragX, // 记录拖拽时上一次的X位置 lastDragY; // 记录拖拽时上一次的Y位置 ball.addEventListener('mousedown', startDragging); ball.addEventListener('touchstart', startDragging); function startDragging(event) { isDragging = true; isAnimating = false; isAnimationAllowed = false; const rect = ball.getBoundingClientRect(); offsetX = event.clientX - rect.left; offsetY = event.clientY - rect.top; // 初始化上一次拖拽位置为当前小球位置 lastDragX = rect.left; lastDragY = rect.top; document.addEventListener('mousemove', drag); document.addEventListener('mouseup', stopDragging); document.addEventListener('touchmove', drag); document.addEventListener('touchend', stopDragging); } function drag(event) { if (!isDragging) return; event.preventDefault(); var newX, newY; if (event.type === 'mousemove') { newX = event.clientX - offsetX; newY = event.clientY - offsetY; } else if (event.type === 'touchmove') { newX = event.touches[0].clientX - offsetX; newY = event.touches[0].clientY - offsetY; } // 用当前位置与上一次位置的差值计算速度 velocityX = newX - lastDragX; velocityY = newY - lastDragY; // 更新上一次位置为当前位置 lastDragX = newX; lastDragY = newY; ball.style.left = newX + "px"; ball.style.top = newY + "px"; } function stopDragging() { isDragging = false; isAnimationAllowed = true; document.removeEventListener('mousemove', drag); document.removeEventListener('mouseup', stopDragging); document.removeEventListener('touchmove', drag); document.removeEventListener('touchend', stopDragging); if (!isAnimating) { animate(); } } function animate() { if (!isAnimationAllowed) return; isAnimating = true; animationFrame = requestAnimationFrame(animate); velocityX *= friction; velocityY += gravity; var newX = ball.offsetLeft + velocityX; var newY = ball.offsetTop + velocityY; if (newY >= floorLevel - ball.offsetHeight) { newY = floorLevel - ball.offsetHeight; velocityY *= -0.6; } if (newX < 0) { newX = 0; velocityX *= -0.6; } else if (newX > window.innerWidth - ball.offsetWidth) { newX = window.innerWidth - ball.offsetWidth; velocityX *= -0.6; } ball.style.left = newX + "px"; ball.style.top = newY + "px"; if (Math.abs(velocityX) < 0.1 && Math.abs(velocityY) < 0.1 && newY >= floorLevel - ball.offsetHeight) { cancelAnimationFrame(animationFrame); isAnimating = false; } }
额外优化建议
- 消除body默认margin的影响,避免后续布局计算出错:
body { margin: 0; }
- 使用
transform: translate()代替修改left/top属性,减少浏览器重排,提升动画性能:
// 替换drag函数中的位置设置 ball.style.transform = `translate(${newX}px, ${newY}px)`; // 替换animate函数中的位置设置 ball.style.transform = `translate(${newX}px, ${newY}px)`;
内容的提问来源于stack exchange,提问作者Mintii
相关产品推荐
相关产品推荐

