Bouncing Balls HTML

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>Bouncing Balls</title> <link rel="stylesheet" href="style.css"> </head> <body> <canvas id="bouncingCanvas"></canvas> <script src="script.js"></script> </body> </html>

Bouncing Balls CSS

html, body { margin: 0; padding: 0; overflow: hidden; background: #111; } canvas { display: block; }

Bouncing Balls JS

const canvas = document.getElementById("bouncingCanvas"); const ctx = canvas.getContext("2d"); function resizeCanvas() { const dpr = window.devicePixelRatio || 1; canvas.width = window.innerWidth * dpr; canvas.height = window.innerHeight * dpr; canvas.style.width = window.innerWidth + "px"; canvas.style.height = window.innerHeight + "px"; ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.scale(dpr, dpr); } window.addEventListener("resize", resizeCanvas); resizeCanvas(); function rand(min, max) { return Math.random() * (max - min) + min; } class Ball { constructor(x, y, dx, dy, radius, color) { this.x = x; this.y = y; this.dx = dx; this.dy = dy; this.radius = radius; this.color = color; } draw() { ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fillStyle = this.color; ctx.fill(); ctx.closePath(); } update() { this.x += this.dx; this.y += this.dy; if (this.x + this.radius > canvas.width / devicePixelRatio || this.x - this.radius < 0) { this.dx *= -1; } if (this.y + this.radius > canvas.height / devicePixelRatio || this.y - this.radius < 0) { this.dy *= -1; } this.draw(); } collideWith(other) { const dx = this.x - other.x; const dy = this.y - other.y; const distance = Math.sqrt(dx * dx + dy * dy); return distance < this.radius + other.radius; } resolveCollision(other) { const xVelocityDiff = this.dx - other.dx; const yVelocityDiff = this.dy - other.dy; const xDist = other.x - this.x; const yDist = other.y - this.y; if (xVelocityDiff * xDist + yVelocityDiff * yDist >= 0) { const angle = -Math.atan2(other.y - this.y, other.x - this.x); const m1 = 1; const m2 = 1; const u1 = rotate(this.dx, this.dy, angle); const u2 = rotate(other.dx, other.dy, angle); const v1 = { x: u1.x * (m1 - m2) / (m1 + m2) + u2.x * 2 * m2 / (m1 + m2), y: u1.y }; const v2 = { x: u2.x * (m2 - m1) / (m1 + m2) + u1.x * 2 * m1 / (m1 + m2), y: u2.y }; const final1 = rotate(v1.x, v1.y, -angle); const final2 = rotate(v2.x, v2.y, -angle); this.dx = final1.x; this.dy = final1.y; other.dx = final2.x; other.dy = final2.y; } } } function rotate(dx, dy, angle) { return { x: dx * Math.cos(angle) - dy * Math.sin(angle), y: dx * Math.sin(angle) + dy * Math.cos(angle) }; } const balls = []; const totalBalls = 50; for (let i = 0; i < totalBalls; i++) { let radius = rand(10, 20); let x = rand(radius, canvas.width / devicePixelRatio - radius); let y = rand(radius, canvas.height / devicePixelRatio - radius); let dx = rand(-2, 2); let dy = rand(-2, 2); let color = `hsl(${Math.random() * 360}, 80%, 60%)`; let newBall = new Ball(x, y, dx, dy, radius, color); let overlapping = false; for (let j = 0; j < balls.length; j++) { if (newBall.collideWith(balls[j])) { overlapping = true; break; } } if (!overlapping) { balls.push(newBall); } else { i--; } } function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (let i = 0; i < balls.length; i++) { for (let j = i + 1; j < balls.length; j++) { if (balls[i].collideWith(balls[j])) { balls[i].resolveCollision(balls[j]); } } } balls.forEach(ball => ball.update()); requestAnimationFrame(animate); } animate();