You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何复刻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;
      }
    }
    
  • Rendering Engine: Uses canvas (or WebGL for high-performance) to draw particles and update the frame. The requestAnimationFrame API 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, and mouseleave events to track:

    • Real-time mouse coordinates (mouseX, mouseY)
    • Whether the mouse is hovering over the canvas (mouseIsOver flag)
  • 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 mouseIsOver flag 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, and pullForce to 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 12:24:07