【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;
相关推荐
Blossom.11814 小时前
Transformer架构优化实战:从MHA到MQA/GQA的显存革命
人工智能·python·深度学习·react.js·架构·aigc·transformer
鹏多多14 小时前
jsx/tsx使用cssModule和typescript-plugin-css-modules
前端·vue.js·react.js
神秘的猪头17 小时前
🎨 CSS 这种“烂大街”的技术,怎么在 React 和 Vue 里玩出花来?—— 模块化 CSS 深度避坑指南
css·vue.js·react.js
3秒一个大18 小时前
模块化 CSS:解决样式污染的前端工程化方案
css·vue.js·react.js
全栈前端老曹18 小时前
【前端路由】React Router 权限路由控制 - 登录验证、私有路由封装、高阶组件实现路由守卫
前端·javascript·react.js·前端框架·react-router·前端路由·权限路由
Amumu1213818 小时前
React应用
前端·react.js·前端框架
前端小臻20 小时前
列举react中类组件和函数组件常用到的方法
前端·javascript·react.js
wayne21421 小时前
React Native 状态管理方案全梳理:Redux、Zustand、React Query 如何选
javascript·react native·react.js
aPurpleBerry21 小时前
React Hooks(数据驱动、副作用、状态传递、状态派生)
前端·react.js·前端框架
前端小臻21 小时前
react没有双向数据绑定是怎么实现数据实时变更的
前端·javascript·react.js