colliding_balls
Version:
This is simple module that will generate a canvas which will have balls that can collide and move randomly.
183 lines (167 loc) • 5.14 kB
JavaScript
function generateRandomPoint(max) {
return Math.ceil(Math.random() * max);
}
function isCollide(a, b, size) {
let squareDistance = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
return squareDistance <= (size + size) * (size + size);
}
export function collidingBalls({
$ele,
count = 100,
isStatic = false,
size = 5,
speed = 7,
sameSize = true,
id = "myCanvas",
color = "#000000",
hollow = false,
borderWidth = 2,
gravity = 0,
followCursor = false,
cursorForce = 0.002,
}) {
if (!$ele) {
return null;
}
const heightC = $ele.offsetHeight;
const widthC = $ele.offsetWidth;
const balls = [];
const placeholder = document.createElement("div");
placeholder.innerHTML = `<canvas id="${id}" height="${heightC}" width="${widthC}" style="position: absolute; top: 0; left: 0;"></canvas>`;
if (!placeholder?.firstElementChild) {
return null;
}
const canvas = placeholder.firstElementChild;
const ctx = canvas.getContext("2d");
const cursorState = {
x: widthC / 2,
y: heightC / 2,
active: false,
};
const getColor = (index) => {
if (typeof color === "function") {
return color(index);
}
if (Array.isArray(color) && color.length) {
return color[index % color.length];
}
return color;
};
if (followCursor && canvas) {
const updateCursorPosition = (event) => {
const bounds = canvas.getBoundingClientRect();
cursorState.x = event.clientX - bounds.left;
cursorState.y = event.clientY - bounds.top;
};
canvas.addEventListener("mouseenter", (event) => {
cursorState.active = true;
updateCursorPosition(event);
});
canvas.addEventListener("mousemove", (event) => {
cursorState.active = true;
updateCursorPosition(event);
});
canvas.addEventListener("mouseleave", () => {
cursorState.active = false;
});
}
function update() {
if (!ctx) {
return null;
}
ctx.clearRect(0, 0, widthC, heightC);
for (let i = 0; i < count; i++) {
balls[i].dy += gravity;
if (followCursor && cursorState.active && cursorForce > 0) {
const toCursorX = cursorState.x - balls[i].x;
const toCursorY = cursorState.y - balls[i].y;
balls[i].dx += toCursorX * cursorForce;
balls[i].dy += toCursorY * cursorForce;
}
balls[i].x += balls[i].dx;
if (balls[i].x > widthC) {
balls[i].dx = -balls[i].dx;
balls[i].x = widthC;
}
if (balls[i].x < 0) {
balls[i].dx = -balls[i].dx;
balls[i].x = 0;
}
balls[i].y += balls[i].dy;
if (balls[i].y > heightC) {
balls[i].dy = -balls[i].dy;
balls[i].y = heightC;
}
if (balls[i].y < 0) {
balls[i].dy = -balls[i].dy;
balls[i].y = 0;
}
ctx.beginPath();
ctx.arc(balls[i].x, balls[i].y, balls[i].size, 0, 2 * Math.PI, true);
if (hollow) {
ctx.lineWidth = Math.max(1, balls[i].borderWidth);
ctx.strokeStyle = balls[i].color;
ctx.stroke();
} else {
ctx.fillStyle = balls[i].color;
ctx.fill();
}
}
for (let i = 0; i < count - 1; i++) {
for (let j = i + 1; j < count; j++) {
if (isCollide(balls[i], balls[j], size)) {
let vCollision = {
x: balls[j].x - balls[i].x,
y: balls[j].y - balls[i].y,
};
let distance = Math.sqrt(
(balls[j].x - balls[i].x) * (balls[j].x - balls[i].x) +
(balls[j].y - balls[i].y) * (balls[j].y - balls[i].y)
);
let vCollisionNorm = {
x: vCollision.x / distance,
y: vCollision.y / distance,
};
let vRelativeVelocity = {
x: balls[i].dx - balls[j].dx,
y: balls[i].dy - balls[j].dy,
};
let speed =
vRelativeVelocity.x * vCollisionNorm.x +
vRelativeVelocity.y * vCollisionNorm.y;
if (speed > 0) {
balls[i].dx -= speed * vCollisionNorm.x;
balls[i].dy -= speed * vCollisionNorm.y;
balls[j].dx += speed * vCollisionNorm.x;
balls[j].dy += speed * vCollisionNorm.y;
}
}
}
}
if (!isStatic) {
window.requestAnimationFrame(update);
}
}
function init() {
if (!ctx) {
return null;
}
for (let i = 0; i < count; i++) {
let x = generateRandomPoint(widthC);
let y = generateRandomPoint(heightC);
balls.push({
x,
y,
size: sameSize ? size : Math.ceil(Math.random() * size),
dx: (Math.random() * 100 > 50 ? 1 : -1) * generateRandomPoint(speed),
dy: (Math.random() * 100 > 50 ? 1 : -1) * generateRandomPoint(speed),
color: getColor(i),
borderWidth,
});
}
$ele.append(canvas);
window.requestAnimationFrame(update);
}
init();
return canvas;
}