如何复刻Google Antigravity页面的鼠标悬停粒子动画及技术实现解析
Hey there! I’ve tinkered with interactive particle systems like Google’s Antigravity before, so let me walk you through how this works step by step, including a working code example.
1. 粒子系统的核心结构
A typical interactive particle system (like the one in Antigravity) has three key components:
Particle Objects: Each particle is an independent entity with core properties to drive its behavior:
- Current position (
x,y) - Velocity (
vx,vy) for movement - Base position (to return to when no interaction is happening)
- Visual properties (size, color)
Here’s a simple JavaScript class to define a particle:
class Particle { constructor(x, y) { this.x = x; this.y = y; this.vx = (Math.random() - 0.5) * 1; // Random initial small velocity this.vy = (Math.random() - 0.5) * 1; this.size = Math.random() * 2 + 1; this.baseX = x; // Store original position for "return" behavior this.baseY = y; } }- Current position (
Rendering Engine: Uses canvas (or WebGL for high-performance) to draw particles and update the frame. The
requestAnimationFrameAPI handles smooth, browser-optimized animation loops.Motion Controller: Manages particle movement logic, including:
- Applying forces from mouse interaction
- Pulling particles back to their base positions
- Adding friction to create natural deceleration
- Boundary checks to prevent particles from flying off-screen
2. How Mouse Interaction Affects Particle Movement
The Antigravity effect relies on detecting mouse input and translating it into forces that push particles away. Here’s the breakdown:
Mouse State Tracking: Listen for
mousemove,mouseenter, andmouseleaveevents to track:- Real-time mouse coordinates (
mouseX,mouseY) - Whether the mouse is hovering over the canvas (
mouseIsOverflag)
- Real-time mouse coordinates (
Force Calculation: For each particle, calculate the distance to the mouse. If it’s within a threshold, apply a repulsive force:
function applyMouseForce(particle) { const dx = particle.x - mouseX; const dy = particle.y - mouseY; const distance = Math.sqrt(dx * dx + dy * dy); const maxInteractionDistance = 120; // Adjust for wider/narrower effect // Push particle away if it's close enough to the mouse if (distance < maxInteractionDistance && mouseIsOver) { const forceStrength = (maxInteractionDistance - distance) / maxInteractionDistance * 4; const angle = Math.atan2(dy, dx); particle.vx += Math.cos(angle) * forceStrength; particle.vy += Math.sin(angle) * forceStrength; } // Pull particle back to its base position when mouse is gone const pullStrength = 0.04; particle.vx += (particle.baseX - particle.x) * pullStrength; particle.vy += (particle.baseY - particle.y) * pullStrength; // Add friction to slow particles naturally particle.vx *= 0.94; particle.vy *= 0.94; }State Transition: When the mouse leaves the canvas, toggle the
mouseIsOverflag off—this lets the "pull back" force take over, returning particles to their original positions smoothly.
3. Full Working Example
Here’s a complete, runnable implementation that mirrors the core behavior of Google’s Antigravity particle effect:
<canvas id="particleCanvas"></canvas> <script> const canvas = document.getElementById('particleCanvas'); const ctx = canvas.getContext('2d'); // Set canvas to window size canvas.width = window.innerWidth; canvas.height = window.innerHeight; let particles = []; let mouseX = 0; let mouseY = 0; let mouseIsOver = false; // Particle class definition class Particle { constructor(x, y) { this.x = x; this.y = y; this.vx = (Math.random() - 0.5) * 1; this.vy = (Math.random() - 0.5) * 1; this.size = Math.random() * 2 + 1; this.baseX = x; this.baseY = y; } update() { applyMouseForce(this); // Update position with velocity this.x += this.vx; this.y += this.vy; // Boundary bounce if (this.x < 0 || this.x > canvas.width) this.vx *= -1; if (this.y < 0 || this.y > canvas.height) this.vy *= -1; } draw() { ctx.fillStyle = '#202124'; // Google's dark gray for particles ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); } } // Mouse force logic function applyMouseForce(particle) { const dx = particle.x - mouseX; const dy = particle.y - mouseY; const distance = Math.sqrt(dx * dx + dy * dy); const maxDistance = 120; if (distance < maxDistance && mouseIsOver) { const force = (maxDistance - distance) / maxDistance * 4; const angle = Math.atan2(dy, dx); particle.vx += Math.cos(angle) * force; particle.vy += Math.sin(angle) * force; } const pullForce = 0.04; particle.vx += (particle.baseX - particle.x) * pullForce; particle.vy += (particle.baseY - particle.y) * pullForce; particle.vx *= 0.94; particle.vy *= 0.94; } // Initialize particle swarm function initParticles() { particles = []; const particleCount = 200; // Adjust for more/less particles for (let i = 0; i < particleCount; i++) { particles.push(new Particle( Math.random() * canvas.width, Math.random() * canvas.height )); } } // Animation loop function animate() { // Semi-transparent background for trail effect ctx.fillStyle = 'rgba(255,255,255,0.05)'; ctx.fillRect(0, 0, canvas.width, canvas.height); particles.forEach(particle => { particle.update(); particle.draw(); }); requestAnimationFrame(animate); } // Mouse event listeners canvas.addEventListener('mousemove', (e) => { mouseX = e.clientX; mouseY = e.clientY; }); canvas.addEventListener('mouseenter', () => { mouseIsOver = true; }); canvas.addEventListener('mouseleave', () => { mouseIsOver = false; }); // Handle window resize window.addEventListener('resize', () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; initParticles(); }); // Start the animation initParticles(); animate(); </script>
4. Tips to Refine the Effect
- Performance Boost: For hundreds/thousands of particles, switch to WebGL instead of Canvas 2D—this is what Google uses for smooth performance.
- Tweak Parameters: Adjust
maxDistance,forceStrength, andpullForceto match the exact "feel" of Antigravity. - Visual Polish: Add gradient colors to particles, make size dependent on velocity, or draw lines between nearby particles for a more connected look.
内容的提问来源于stack exchange,提问作者Durkshaw

