如何实现多个可移动小球效果?前端技术求助
如何添加多个可移动弹跳小球并实现运动效果?
我想实现多个可移动小球的弹跳运动效果,但当前代码存在问题——无法在创建新对象时生成有效的实例,尝试用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
相关产品推荐
相关产品推荐

