Particles
Home
Snippets
Particles
HTML
CSS
JS
<div id="morph-button"> Click to Morph </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.min.js"></script>
* { margin: 0; padding: 0; box-sizing: border-box; } html, body { width: 100%; height: 100%; overflow: hidden; background: #000; font-family: 'Inter', sans-serif; } canvas { display: block; position: absolute; top: 0; left: 0; z-index: 1; } @keyframes rotateGlow { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } #morph-button { position: absolute; bottom: 30px; left: 50%; transform: translateX(-50%); z-index: 10; padding: 14px 28px; border-radius: 15px; overflow: hidden; text-align: center; font-size: 14px; color: rgba(255, 255, 255, 0.95); background: rgba(25, 25, 25, 0.4); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border: 1px solid rgba(255, 255, 255, 0.15); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); transition: transform 0.2s ease; cursor: pointer; user-select: none; } #morph-button:hover { transform: translateX(-50%) scale(1.05); } #morph-button:active { transform: translateX(-50%) scale(0.98); transition-duration: 0.1s; } #morph-button::before { content: ''; position: absolute; inset: -4px; z-index: -1; background: conic-gradient( from 0deg, rgba(255,255,255,0) 0%, #ffffff 50%, rgba(255,255,255,0) 100% ); filter: blur(8px); border-radius: inherit; animation: rotateGlow 8s linear infinite; }
const PARTICLES_COUNT = 1200; const MAX_SPEED = 6.0; const MAX_FORCE = 0.3; const MORPH_DURATION = 45; const CIRCLE = 0; const SQUARE = 1; const TRIANGLE = 2; const STAR = 3; const TOTAL_SHAPES = 4; let particles = []; let SHAPE_RADIUS; let currentShape = 0; let targetShape = 0; let isMorphing = false; let morphFrame = 0; class Particle { constructor() { this.pos = createVector(); this.vel = createVector(); this.acc = createVector(); this.prevPos = createVector(); this.maxSpeed = random(1, MAX_SPEED); this.maxForce = random(0.05, MAX_FORCE); this.size = random(0.5, 2.5); } seek(target) { let desired = p5.Vector.sub(target, this.pos); desired.setMag(this.maxSpeed); let steer = p5.Vector.sub(desired, this.vel); steer.limit(this.maxForce); this.applyForce(steer); } applyForce(force) { this.acc.add(force); } update() { this.vel.add(this.acc); this.vel.limit(this.maxSpeed); this.pos.add(this.vel); this.acc.mult(0); } draw() { const speed = this.vel.mag(); const brightness = map(speed, 0, this.maxSpeed, 60, 100); const alpha = map(speed, 0, this.maxSpeed, 0.1, 0.8); const weight = map(speed, 0, this.maxSpeed, this.size * 0.5, this.size); stroke(0, 0, brightness, alpha); strokeWeight(weight); line(this.pos.x, this.pos.y, this.prevPos.x, this.prevPos.y); this.prevPos.set(this.pos); } } function getCirclePos(angle, radius) { return createVector(cos(angle) * radius, sin(angle) * radius); } function getSquarePos(angle, radius) { let a = (angle + PI / 4) % TWO_PI; if (a < 0) a += TWO_PI; let t = tan(a); let x = radius * Math.sign(cos(a)); let y = radius * t * Math.sign(cos(a)); if (abs(y) > radius) { y = radius * Math.sign(y); x = radius / t * Math.sign(y); } return createVector(x,y); } function getTrianglePos(angle, radius) { angle -= PI / 2; let a = angle % TWO_PI; if (a < 0) a += TWO_PI; const sideIndex = floor(a / (TWO_PI / 3)); const angleOnSide = a % (TWO_PI / 3); const p1 = createVector(cos(sideIndex * TWO_PI / 3), sin(sideIndex * TWO_PI / 3)).mult(radius); const p2 = createVector(cos((sideIndex + 1) * TWO_PI / 3), sin((sideIndex + 1) * TWO_PI / 3)).mult(radius); return p5.Vector.lerp(p1, p2, angleOnSide / (TWO_PI/3)); } function getStarPos(angle, radius) { const outerRadius = radius; const innerRadius = radius * 0.5; const numPoints = 5; const angleStep = TWO_PI / (numPoints * 2); let a = angle - PI / 2; const segment = floor(a / angleStep); const startAngleOfSegment = segment * angleStep; const r1 = (segment % 2 === 0) ? outerRadius : innerRadius; const r2 = (segment % 2 === 0) ? innerRadius : outerRadius; const p1_angle = startAngleOfSegment; const p2_angle = startAngleOfSegment + angleStep; const p1 = createVector(cos(p1_angle) * r1, sin(p1_angle) * r1); const p2 = createVector(cos(p2_angle) * r2, sin(p2_angle) * r2); const t = (a - startAngleOfSegment) / angleStep; return p5.Vector.lerp(p1, p2, t); } function setup() { createCanvas(windowWidth, windowHeight); colorMode(HSB, 360, 100, 100, 1); background(0); SHAPE_RADIUS = min(width, height) * 0.3; for (let i = 0; i < PARTICLES_COUNT; i++) { let p = new Particle(); p.pos.set(random(-width / 2, width / 2), random(-height / 2, height / 2)); p.prevPos.set(p.pos); particles.push(p); } const morphButton = document.getElementById('morph-button'); morphButton.addEventListener('click', () => { if (!isMorphing) { isMorphing = true; morphFrame = 0; targetShape = (currentShape + 1) % TOTAL_SHAPES; } }); } function draw() { background(0, 0, 0, 0.25); translate(width / 2, height / 2); if (isMorphing) { morphFrame++; if (morphFrame >= MORPH_DURATION) { isMorphing = false; morphFrame = 0; currentShape = targetShape; } } for (let i = 0; i < particles.length; i++) { const p = particles[i]; const angle = map(i, 0, PARTICLES_COUNT, 0, TWO_PI); const fromShapePos = getShapePosition(currentShape, angle, SHAPE_RADIUS); let targetPos; if (isMorphing) { const toShapePos = getShapePosition(targetShape, angle, SHAPE_RADIUS); const easedProgress = easeInOutCubic(morphFrame / MORPH_DURATION); targetPos = p5.Vector.lerp(fromShapePos, toShapePos, easedProgress); } else { targetPos = fromShapePos; } p.seek(targetPos); p.update(); p.draw(); } } function getShapePosition(shape, angle, radius) { switch (shape) { case CIRCLE: return getCirclePos(angle, radius); case SQUARE: return getSquarePos(angle, radius); case TRIANGLE: return getTrianglePos(angle, radius); case STAR: return getStarPos(angle, radius); default: return createVector(0, 0); } } function windowResized() { resizeCanvas(windowWidth, windowHeight); SHAPE_RADIUS = min(width, height) * 0.3; for (let i = 0; i < particles.length; i++) { const p = particles[i]; const angle = map(i, 0, PARTICLES_COUNT, 0, TWO_PI); const resetPos = getShapePosition(currentShape, angle, SHAPE_RADIUS); p.pos.set(resetPos); p.prevPos.set(resetPos); } background(0); } function easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : 1 - pow(-2 * t + 2, 3) / 2; }
Ad #1
Ad #2
Scroll to Top