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();