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

如何实现多个可移动小球效果?前端技术求助

如何添加多个可移动弹跳小球并实现运动效果?

我想实现多个可移动小球的弹跳运动效果,但当前代码存在问题——无法在创建新对象时生成有效的实例,尝试用jQuery添加小球元素也没成功。以下是我现有的小球及弹跳逻辑代码:

现有HTML代码

<div class="ball" id="ball"></div>

现有CSS代码

*,
*::after,
*::before {
  box-sizing: border-box;
}

:root {
  --hue: 200;
  --saturation: 50%;
  --foreground-color: hsl(var(--hue), var(--saturation), 75%);
  --background-color: hsl(var(--hue), var(--saturation), 20%);
}

body {
  margin: 0;
  overflow: hidden;
}

.ball {
  --x: 50;
  --y: 50;
  position: absolute;
  background-color: black;
  left: calc(var(--x) * 1vw);
  top: calc(var(--y) * 1vh);
  border-radius: 50%;
  transform: translate(-50%, -50%);
  width: 3vh;
  height: 3vh;
}

现有JavaScript代码

// code for ball
const INITIAL_VELOCITY = 0.085
const VELOCITY_CHANGE_RATE = 0
class Ball {
  constructor(ballElem) {
    this.ballElem = ballElem
    this.reset()
  }
  get x() {
    return parseFloat(getComputedStyle(this.ballElem).getPropertyValue("--x"))
  }
  set x(value) {
    this.ballElem.style.setProperty("--x", value)
  }
  get y() {
    return parseFloat(getComputedStyle(this.ballElem).getPropertyValue("--y"))
  }
  set y(value) {
    this.ballElem.style.setProperty("--y", value)
  }
  rect() {
    return this.ballElem.getBoundingClientRect()
  }
  reset() {
    this.x = 50
    this.y = 50
    this.direction = {x : 0}
    while (Math.abs(this.direction.x) <= 0.1 || Math.abs(this.direction.x) >= 0.9) {
      const heading = randomNumberBetween(0, 2 * Math.PI)
      this.direction = { x: Math.cos(heading), y: Math.sin(heading) }
    }
    this.velocity = INITIAL_VELOCITY
  }
update(delta,index) {
console.log(delta,index)
    this.x += this.direction.x * this.velocity * delta
    this.y += this.direction.y * this.velocity * delta
    const rect = this.rect()

    if (rect.bottom >= window.innerHeight || rect.top <= 0) {
      this.direction.y *= -1
    }
    if (rect.right >= window.innerWidth || rect.left <= 0) {
      this.direction.x *= -1
    }

    //background color
    const hue = parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--hue"));
    document.documentElement.style.setProperty("--hue", hue + delta * 0.02)
  }
}
function randomNumberBetween(min, max) {
  return Math.random() * (max - min) + min
}
var ballarr = []

for(var i = 0; i <= 10; i++){
    document.querySelector('body').innerHTML +=  `<div class="ball" id="ball${i}"></div>`

    ballarr[i] = new Ball(document.querySelector(`#ball${i}`))
}

console.log(ballarr)

let lastTime
function updateTime(time) {
  if (lastTime != null) {
    const delta = time -  lastTime
    
   ballarr.map((val, index) => {
   val.update(delta, index)
   
   })
  }
  lastTime = time
  window.requestAnimationFrame(updateTime)
}

问题分析与修复方案

核心问题是用innerHTML +=添加元素时,会重新渲染整个body的DOM,导致之前创建的Ball实例绑定的元素失效。另外依赖id查找元素没必要,直接操作DOM元素更可靠。下面是修复后的完整代码:

修复后的完整代码

HTML

<!-- 无需预先写ball元素,JS会动态创建 -->

CSS

*,
*::after,
*::before {
  box-sizing: border-box;
}

:root {
  --hue: 200;
  --saturation: 50%;
  --background-color: hsl(var(--hue), var(--saturation), 20%);
}

body {
  margin: 0;
  overflow: hidden;
  background-color: var(--background-color);
}

.ball {
  --x: 50;
  --y: 50;
  position: absolute;
  left: calc(var(--x) * 1vw);
  top: calc(var(--y) * 1vh);
  border-radius: 50%;
  transform: translate(-50%, -50%);
  width: 3vh;
  height: 3vh;
}

JavaScript

const INITIAL_VELOCITY = 0.085
const VELOCITY_CHANGE_RATE = 0

class Ball {
  constructor(ballElem) {
    this.ballElem = ballElem
    this.reset()
    // 给每个小球设置随机颜色
    this.ballElem.style.backgroundColor = `hsl(${randomNumberBetween(0, 360)}, 70%, 60%)`
  }

  get x() {
    return parseFloat(getComputedStyle(this.ballElem).getPropertyValue("--x"))
  }

  set x(value) {
    this.ballElem.style.setProperty("--x", value)
  }

  get y() {
    return parseFloat(getComputedStyle(this.ballElem).getPropertyValue("--y"))
  }

  set y(value) {
    this.ballElem.style.setProperty("--y", value)
  }

  rect() {
    return this.ballElem.getBoundingClientRect()
  }

  reset() {
    // 随机初始位置,避免所有球堆在一起
    this.x = randomNumberBetween(10, 90)
    this.y = randomNumberBetween(10, 90)
    this.direction = { x: 0 }
    while (Math.abs(this.direction.x) <= 0.1 || Math.abs(this.direction.x) >= 0.9) {
      const heading = randomNumberBetween(0, 2 * Math.PI)
      this.direction = { x: Math.cos(heading), y: Math.sin(heading) }
    }
    this.velocity = INITIAL_VELOCITY
  }

  update(delta) {
    this.x += this.direction.x * this.velocity * delta
    this.y += this.direction.y * this.velocity * delta

    const rect = this.rect()

    // 边界反弹逻辑
    if (rect.bottom >= window.innerHeight || rect.top <= 0) {
      this.direction.y *= -1
    }
    if (rect.right >= window.innerWidth || rect.left <= 0) {
      this.direction.x *= -1
    }
  }
}

function randomNumberBetween(min, max) {
  return Math.random() * (max - min) + min
}

const ballarr = []
const ballCount = 10 // 要创建的小球数量

// 动态创建小球元素并实例化Ball对象
for (let i = 0; i < ballCount; i++) {
  const ballElem = document.createElement('div')
  ballElem.classList.add('ball')
  document.body.appendChild(ballElem)
  ballarr.push(new Ball(ballElem))
}

let lastTime
function updateTime(time) {
  if (lastTime != null) {
    const delta = time - lastTime
    
    // 更新所有小球状态
    ballarr.forEach(ball => ball.update(delta))
    
    // 只更新一次背景色,避免多个小球重复修改导致跳变
    const hue = parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--hue"))
    document.documentElement.style.setProperty("--hue", hue + delta * 0.02)
  }
  lastTime = time
  window.requestAnimationFrame(updateTime)
}

// 启动动画循环
window.requestAnimationFrame(updateTime)

关键修改点

  • DOM创建方式优化:用document.createElement和appendChild代替innerHTML +=,避免DOM重绘导致实例失效。
  • 移除id依赖:直接将创建的DOM元素传给Ball构造函数,不需要通过id查找。
  • 随机初始位置与颜色:让每个小球初始位置和颜色不同,更容易区分。
  • 背景色更新优化:把背景色更新移到主循环中,只执行一次,避免多个小球重复修改导致颜色跳变。
  • 启动动画循环:添加主动调用requestAnimationFrame(updateTime),确保动画正常启动。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 13:55:20