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

弹跳小球动画仅向右滚动异常的原因及修复咨询

问题原因

问题出在拖拽时速度计算依赖的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;
  }
}

额外优化建议

  1. 消除body默认margin的影响,避免后续布局计算出错:
body {
  margin: 0;
}
  1. 使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 04:59:50