技术咨询:如何复刻谷歌Antigravity页面的鼠标悬停粒子动画效果
嘿,我正好研究过谷歌Antigravity那个粒子交互效果,核心是Canvas粒子系统加上鼠标力场模拟,我来一步步拆解给你看:
1. 粒子系统的结构设计
一个稳定的粒子系统得包含这几个核心部分:
- 粒子类(Particle):每个粒子要存储自身的运动状态——位置(x,y)、速度(vx,vy)、加速度(ax,ay),再加上半径、颜色这些视觉属性。核心逻辑是每一帧用物理公式更新状态:
速度 = 速度 + 加速度,位置 = 位置 + 速度,更新后要重置加速度,避免粒子持续受力失控。 - 渲染循环(Render Loop):用
requestAnimationFrame驱动每一帧的更新与渲染,先以半透明色清屏(能留下粒子拖影,更有动感),再遍历所有粒子,先更新运动状态再绘制(用Canvas的arc方法画圆形粒子)。 - 边界处理:粒子碰到画布边缘时要反弹,比如x坐标超出画布宽度时反转vx方向,同时加一点摩擦系数(比如0.8)让速度衰减,模拟真实世界的碰撞损耗,效果更自然。
2. 鼠标交互的处理逻辑
鼠标对粒子的影响本质是力场模拟,关键步骤如下:
- 鼠标状态追踪:监听
mousemove事件记录鼠标实时坐标(mouseX, mouseY),用mouseenter/mouseleave标记鼠标是否在画布范围内,避免鼠标移出后粒子还受影响。 - 引力/斥力计算:对每个粒子,先计算它与鼠标的直线距离。如果距离小于设定的影响半径,就用向量计算力的方向:
- 若要实现粒子远离鼠标(像Antigravity的效果),就取粒子指向鼠标的反方向作为力的方向;
- 力的大小和距离成反比——距离越近,力越大,比如用
(影响半径 - 距离) / 影响半径作为力的系数,让效果更平滑。
- 悬停增强效果:如果需要在鼠标悬停特定元素(比如按钮)时让粒子反应更强烈,可以临时增大影响半径或力的系数,强化交互反馈。
实现示例代码
下面是一个简化版的复刻代码,直接运行就能看到类似效果:
<canvas id="particleCanvas"></canvas> <style> body { margin: 0; overflow: hidden; } #particleCanvas { background: #fff; } </style> <script> const canvas = document.getElementById('particleCanvas'); const ctx = canvas.getContext('2d'); // 适配窗口大小 canvas.width = window.innerWidth; canvas.height = window.innerHeight; // 鼠标状态管理 let mouse = { x: null, y: null, isActive: false, influenceRadius: 180 // 鼠标影响范围 }; // 粒子类 class Particle { constructor() { this.reset(); this.radius = Math.random() * 3 + 0.5; this.color = '#1a73e8'; // 谷歌主题蓝 } reset() { // 随机初始位置 this.x = Math.random() * canvas.width; this.y = Math.random() * canvas.height; // 小范围随机初始速度 this.vx = (Math.random() - 0.5) * 0.6; this.vy = (Math.random() - 0.5) * 0.6; this.ax = 0; this.ay = 0; } update() { // 应用加速度到速度 this.vx += this.ax; this.vy += this.ay; // 应用速度到位置 this.x += this.vx; this.y += this.vy; // 重置加速度 this.ax = 0; this.ay = 0; // 边界反弹+摩擦 if (this.x < 0 || this.x > canvas.width) { this.vx *= -0.8; this.x = Math.max(0, Math.min(canvas.width, this.x)); } if (this.y < 0 || this.y > canvas.height) { this.vy *= -0.8; this.y = Math.max(0, Math.min(canvas.height, this.y)); } // 鼠标斥力逻辑 if (mouse.isActive) { const dx = this.x - mouse.x; const dy = this.y - mouse.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < mouse.influenceRadius && distance > 0) { const force = (mouse.influenceRadius - distance) / mouse.influenceRadius; const dirX = dx / distance; const dirY = dy / distance; // 给粒子添加加速度 this.ax += dirX * force * 0.6; this.ay += dirY * force * 0.6; } } // 速度衰减,防止无限加速 this.vx *= 0.985; this.vy *= 0.985; } draw() { ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fillStyle = this.color; ctx.fill(); } } // 创建粒子群 const particles = []; const particleCount = 300; for (let i = 0; i < particleCount; i++) { particles.push(new Particle()); } // 渲染循环 function animate() { // 半透明清屏,保留拖影 ctx.fillStyle = 'rgba(255,255,255,0.08)'; ctx.fillRect(0, 0, canvas.width, canvas.height); particles.forEach(particle => { particle.update(); particle.draw(); }); requestAnimationFrame(animate); } // 绑定鼠标事件 canvas.addEventListener('mousemove', (e) => { mouse.x = e.clientX; mouse.y = e.clientY; }); canvas.addEventListener('mouseenter', () => mouse.isActive = true); canvas.addEventListener('mouseleave', () => mouse.isActive = false); // 窗口 resize 适配 window.addEventListener('resize', () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; particles.forEach(p => p.reset()); }); // 启动动画 animate(); </script>
额外优化建议
- 可以给近距离的粒子添加连线效果,当粒子间距小于某个值时用
lineTo画线段,更贴近谷歌Antigravity的视觉感; - 调整粒子数量、力的系数、摩擦系数来平衡性能与效果,一般200-500个粒子在普通设备上都能流畅运行;
- 可以给鼠标悬停时添加粒子颜色变化、影响半径渐变的效果,增强交互的层次感。
内容的提问来源于stack exchange,提问作者Durkshaw
相关产品推荐
相关产品推荐

