live - 60fps 1920 x 1080
<!DOCTYPE html>
<html>
<head>
<title>Particle Collision Reaction</title>
<style>
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #050505;
}
.collision-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
filter: brightness(1.2);
}
</style>
</head>
<body>
<canvas class="collision-canvas"></canvas>
<script>
const canvas = document.querySelector('.collision-canvas');
const ctx = canvas.getContext('2d');
let width, height;
let particles = [];
let sparks = [];
function setSize() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
}
class Particle {
constructor(type) {
this.type = type; // 0 for left, 1 for right
this.reset();
}
reset(){
this.x = (this.type === 0) ? -5 : width + 5;
this.y = Math.random() * height;
this.vx = (this.type === 0) ? 2 + Math.random() * 2 : -2 - Math.random() * 2;
this.vy = Math.random() - 0.5;
this.hue = (this.type === 0) ? 180 : 330;
this.life = 100 + Math.random() * 100;
}
update(){
this.x += this.vx;
this.y += this.vy;
this.life--;
// Check collision zone and life
const distToCenter = Math.abs(this.x - width/2);
if (this.life <= 0 || (distToCenter < 50 && Math.random() > 0.8)) {
for(let i=0; i < 5; i++) {
sparks.push(new Spark(this.x, this.y, this.hue));
}
this.reset();
}
}
draw() {
ctx.fillStyle = `hsl(${this.hue}, 100%, 70%)`;
ctx.beginPath();
ctx.arc(this.x, this.y, 1.5, 0, Math.PI * 2);
ctx.fill();
}
}
class Spark {
constructor(x, y, hue){
this.x = x;
this.y = y;
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 3 + 1;
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
this.life = 50;
this.hue = hue + Math.random()*40-20;
}
update(){
this.x += this.vx;
this.y += this.vy;
this.life--;
}
draw(){
const alpha = this.life / 50;
ctx.fillStyle = `hsla(${this.hue}, 100%, 80%, ${alpha})`;
ctx.beginPath();
ctx.arc(this.x, this.y, 1, 0, Math.PI * 2);
ctx.fill();
}
}
function init() {
setSize();
particles = [];
sparks = [];
for(let i=0; i<150; i++){
particles.push(new Particle(0));
particles.push(new Particle(1));
}
}
function animate(){
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle = 'rgba(0,0,0,0.1)';
ctx.fillRect(0,0,width,height);
ctx.globalCompositeOperation = 'lighter';
particles.forEach(p => { p.update(); p.draw(); });
for(let i=sparks.length-1; i>=0; i--){
sparks[i].update();
sparks[i].draw();
if(sparks[i].life <= 0) sparks.splice(i,1);
}
requestAnimationFrame(animate);
}
window.addEventListener('resize', init);
init();
animate();
</script>
</body>
</html> About this animation
Particles colliding and reacting.
Under the hood
Elastic Collision Math
An incredibly impressive display of rigid body physics. When two particles intersect, the script calculates conservation of momentum and energy based on the particle mass and angle of incidence to perfectly ricochet them across the screen via
Math.atan2.