UNPKG

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
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; }