【Gemini简直无敌了】掌间星河:通过MediaPipe实现手势控制粒子

受掘金大佬"不如摸鱼去"的启发,我也试试 Gemini 3做一下手势+粒子交互, 确实学到了不少东西,在这里简单的分享下。 github地址:掌间星河:github.com/huijieya/Ge...

javascript 复制代码
基于原生h5、浏览器、PC摄像头实现手势控制粒子特效交互的逻辑,粒子默认离散,类似银河系分布缓慢移动,同时有5种手势:

手势1: 握拳,握拳后粒子聚拢显示爱心的形状

手势2: 展开手掌并挥手,展开手掌挥手后粒子从当前状态恢复到离散状态

手势3: 👆 比 1 :只有食指伸直,其他 3 根弯曲,此时粒子聚拢显示第一句话:春来夏往

手势4: ✌ 比 2 :只有食指和中指伸直,其他 2 根弯曲手此时粒子聚拢显示第二句话: 秋收冬藏

手势5: 👌 比 3 :只有中指、无名指、小指伸直,食指弯曲,此时粒子聚拢显示第三句话:我们来日方长

效果展示

源码地址

掌间星河:github.com/huijieya/Ge...

源码分析

手势识别流程

1. 手部检测初始化

typescript 复制代码
// HandTracker.tsx 中初始化 MediaPipe Hands
const hands = new (window as any).Hands({
  locateFile: (file: string) => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`
});

hands.setOptions({
  maxNumHands: 1,           // 最多检测一只手
  modelComplexity: 1,       // 模型复杂度
  minDetectionConfidence: 0.7,  // 最小检测置信度
  minTrackingConfidence: 0.7    // 最小跟踪置信度
});

2. 手部关键点获取

MediaPipe 会检测手部的21个关键点(landmarks),每个关键点包含 x, y, z 坐标:

  • 指尖: 4(拇指), 8(食指), 12(中指), 16(无名指), 20(小指)
  • 指关节: 用于判断手指是否伸直

3. 手势分类逻辑

typescript 复制代码
// gestureLogic.ts 中的 classifyGesture 函数
const isExtended = (tipIdx: number, mcpIdx: number) => landmarks[tipIdx].y < landmarks[mcpIdx].y;

// 判断各手指是否伸直
const indexExt = isExtended(8, 5);   // 食指
const middleExt = isExtended(12, 9); // 中指
const ringExt = isExtended(16, 13);  // 无名指
const pinkyExt = isExtended(20, 17); // 小指

// 根据手指状态识别不同手势
if (!indexExt && !middleExt && !ringExt && !pinkyExt) {
  return GestureType.HEART; // 握拳 - 显示爱心
}
if (indexExt && middleExt && ringExt && pinkyExt) {
  return GestureType.GALAXY; // 手掌展开 - 银河状态
}
// ... 其他手势判断

粒子绘制机制

1. 粒子系统初始化

typescript 复制代码
// ParticleCanvas.tsx 中初始化粒子
useEffect(() => {
  const particles: Particle[] = [];
  for (let i = 0; i < PARTICLE_COUNT; i++) {
    particles.push({
      x: Math.random() * window.innerWidth,     // 随机初始位置
      y: Math.random() * window.innerHeight,
      targetX: Math.random() * window.innerWidth, // 目标位置
      targetY: Math.random() * window.innerHeight,
      vx: 0,                                    // 速度
      vy: 0,
      size: Math.random() * 1.5 + 0.5,          // 大小
      color: COLORS[Math.floor(Math.random() * COLORS.length)], // 颜色
      alpha: Math.random() * 0.4 + 0.4,         // 透明度
    });
  }
  particlesRef.current = particles;
}, []);

2. 形状生成算法

typescript 复制代码
const getShapePoints = (type: GestureType, width: number, height: number): Point[] => {
  const centerX = width / 2;
  const centerY = height / 2;
  
  switch (type) {
    case GestureType.HEART: 
      // 心形方程参数化生成点
      // x = 16sin³(t)
      // y = 13cos(t) - 5cos(2t) - 2cos(3t) - cos(4t)
      
    case GestureType.TEXT_1/2/3:
      // 使用 Canvas 绘制文字并提取像素点
      
    case GestureType.GALAXY:
    default:
      // 螺旋银河形状
      const angle = Math.random() * Math.PI * 2;
      const r = Math.pow(Math.random(), 0.7) * maxRadius;
      const spiralFactor = 2.0;
      const offset = r * (spiralFactor / maxRadius) * 5;
      points.push({ 
        x: centerX + Math.cos(angle + offset) * r, 
        y: centerY + Math.sin(angle + offset) * r 
      });
  }
}

3. 粒子动画更新

typescript 复制代码
// 粒子运动和渲染循环
const render = () => {
  // 半透明背景覆盖产生拖尾效果
  ctx.fillStyle = 'rgba(0, 0, 0, 0.18)';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  
  particlesRef.current.forEach((p) => {
    // 平滑插值移动到目标位置
    p.x += (tx - p.x) * LERP_FACTOR;
    p.y += (ty - p.y) * LERP_FACTOR;
    
    // 手势交互影响粒子位置
    if (hPos && canInteract) {
      const dx = p.x - hPos.x;
      const dy = p.y - hPos.y;
      const distSq = dx * dx + dy * dy;
      if (distSq < INTERACTION_RADIUS * INTERACTION_RADIUS) {
        // 排斥力计算
        const dist = Math.sqrt(distSq);
        const force = (1 - dist / INTERACTION_RADIUS) * INTERACTION_STRENGTH;
        p.x += dx * force;
        p.y += dy * force;
      }
    }
    
    // 添加随机扰动使粒子更生动
    p.x += (Math.random() - 0.5) * 0.6;
    p.y += (Math.random() - 0.5) * 0.6;
    
    // 绘制粒子
    ctx.fillStyle = p.color;
    ctx.globalAlpha = p.alpha;
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
    ctx.fill();
  });
  
  animationRef.current = requestAnimationFrame(render);
};

4. 银河旋转效果

typescript 复制代码
// 银河状态下的旋转动画
galaxyAngleRef.current += GALAXY_ROTATION_SPEED;
const cosA = Math.cos(galaxyAngleRef.current);
const sinA = Math.sin(galaxyAngleRef.current);

// 对每个粒子应用旋转变换
const dx = p.targetX - cx;
const dy = p.targetY - cy;
tx = cx + dx * cosA - dy * sinA;
ty = cy + dx * sinA + dy * cosA;
相关推荐
San30.2 小时前
深度驱动:React Hooks 核心之 `useState` 与 `useEffect` 实战详解
前端·javascript·react.js
huohuopro2 小时前
LangChain | LangGraph V1教程 #3 从路由器到ReAct架构
前端·react.js·langchain
打小就很皮...3 小时前
React 实现富文本(使用篇&Next.js)
前端·react.js·富文本·next.js
开发者小天4 小时前
react的拖拽组件库dnd-kit
前端·react.js·前端框架
全栈前端老曹5 小时前
【ReactNative】核心组件与 JSX 语法
前端·javascript·react native·react.js·跨平台·jsx·移动端开发
xiaoxue..6 小时前
React 之 Hooks
前端·javascript·react.js·面试·前端框架
风止何安啊7 小时前
🚀别再卷 Redux 了!Zustand 才是 React 状态管理的躺平神器
前端·react.js·面试
咸鱼加辣10 小时前
【前端框架】react
前端·react.js·前端框架
Lethehong10 小时前
React构建实时股票分析系统:蓝耘MaaS平台与DeepSeek-V3.2的集成实践
前端·react.js·前端框架·蓝耘mcp·蓝耘元生代·蓝耘maas