AI真好玩系列-WebGL爱心粒子手势互动教程 | Interactive Heart Particles with Hand Gestures
-
- [💖 提示词](#💖 提示词)
- [📚 项目代码](#📚 项目代码)
- [🌟 项目简介 | Project Introduction](#🌟 项目简介 | Project Introduction)
- [📌 前提条件 | Prerequisites](#📌 前提条件 | Prerequisites)
- [🚀 核心技术栈 | Core Technologies](#🚀 核心技术栈 | Core Technologies)
- [💖 爱心数学公式 | Heart Math Formula](#💖 爱心数学公式 | Heart Math Formula)
- [👐 手势识别原理 | Gesture Recognition Principle](#👐 手势识别原理 | Gesture Recognition Principle)
- [🧩 核心代码片段 | Core Code Snippets](#🧩 核心代码片段 | Core Code Snippets)
-
- [1. 粒子系统初始化 | Particle System Initialization](#1. 粒子系统初始化 | Particle System Initialization)
- [2. 手势控制逻辑 | Gesture Control Logic](#2. 手势控制逻辑 | Gesture Control Logic)
- [3. 粒子动画循环 | Animation Loop](#3. 粒子动画循环 | Animation Loop)
- [🎨 视觉效果亮点 | Visual Highlights](#🎨 视觉效果亮点 | Visual Highlights)
- [🛠️ 使用指南 | Run Guide](#🛠️ 使用指南 | Run Guide)
-
- [本地运行 | Local Run](#本地运行 | Local Run)
- [🔧 定制项 | Customization Options](#🔧 定制项 | Customization Options)
- [🐛 常见问题 | Troubleshooting](#🐛 常见问题 | Troubleshooting)
- [📚 扩展学习资源 | Extended Resources](#📚 扩展学习资源 | Extended Resources)
- [Conclusion | 结语](#Conclusion | 结语)
周末的深夜,睡不着觉,闲来无事,打开Gemini哈基米玩一把,废话不多说,先上图看最终效果~
碎碎念:我很想上传视频,但是好像有的博客不支持,这里先放个图片
视频地址:https://www.bilibili.com/video/BV1Q92RBoE6F?t=4.9


💖 提示词
创建一个基于WebGL的交互式3D网页,通过摄像头检测用户手掌的张合,实时控制由粒子构成的爱心模型的聚散状态。

📚 项目代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>WebGL 手势控制爱心粒子</title>
<style>
body { margin: 0; overflow: hidden; background-color: #000; }
#canvas-container { width: 100vw; height: 100vh; position: absolute; top: 0; left: 0; z-index: 1; }
/* 隐藏摄像头视频流,只用于后台分析 */
.input_video { display: none; }
#loading {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
color: white; font-family: sans-serif; font-size: 24px; pointer-events: none; z-index: 10;
}
</style>
<!-- 引入 Three.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<!-- 引入 MediaPipe Hands -->
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>
</head>
<body>
<div id="loading">正在加载模型与摄像头...<br>请允许摄像头权限</div>
<div id="canvas-container"></div>
<video class="input_video"></video>
<script>
// --- 1. Three.js 场景初始化 ---
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 30;
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('canvas-container').appendChild(renderer.domElement);
// --- 2. 创建爱心粒子系统 ---
const particleCount = 3000; // 粒子数量
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const targetPositions = new Float32Array(particleCount * 3); // 存储爱心形状的目标位置
const randomPositions = new Float32Array(particleCount * 3); // 存储散开时的随机位置
// 爱心方程函数
function getHeartPosition(t, scale = 1) {
const x = 16 * Math.pow(Math.sin(t), 3);
const y = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t);
const z = 0;
return { x: x * scale, y: y * scale, z: z * scale };
}
for (let i = 0; i < particleCount; i++) {
// 生成爱心形状的目标点
// 为了让爱心立体一点,我们随机分布t,并在Z轴加一点随机扰动
const t = Math.random() * Math.PI * 2;
const scale = 0.5;
const heartPos = getHeartPosition(t, scale);
// 填充爱心内部 (随机缩放)
const r = Math.sqrt(Math.random());
targetPositions[i * 3] = heartPos.x * r;
targetPositions[i * 3 + 1] = heartPos.y * r;
targetPositions[i * 3 + 2] = (Math.random() - 0.5) * 5; // Z轴厚度
// 生成散开的随机位置 (爆炸效果)
randomPositions[i * 3] = (Math.random() - 0.5) * 100;
randomPositions[i * 3 + 1] = (Math.random() - 0.5) * 60;
randomPositions[i * 3 + 2] = (Math.random() - 0.5) * 50;
// 初始位置设为散开状态
positions[i * 3] = randomPositions[i * 3];
positions[i * 3 + 1] = randomPositions[i * 3 + 1];
positions[i * 3 + 2] = randomPositions[i * 3 + 2];
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
// 粒子材质
const material = new THREE.PointsMaterial({
color: 0xff69b4, // 热粉色
size: 0.4,
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending
});
const particles = new THREE.Points(geometry, material);
scene.add(particles);
// --- 3. 交互逻辑变量 ---
let gatherFactor = 0; // 0 = 完全散开, 1 = 完全聚合成爱心
let targetGatherFactor = 0; // 目标聚合度,由手势控制
// --- 4. MediaPipe Hands 配置 ---
const videoElement = document.getElementsByClassName('input_video')[0];
function onResults(results) {
document.getElementById('loading').style.display = 'none';
if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
const landmarks = results.multiHandLandmarks[0];
// 计算手掌开合程度
// 简单算法:计算拇指指尖(4)与其他四指指尖(8,12,16,20)到手腕(0)的平均距离
const wrist = landmarks[0];
const fingerTips = [4, 8, 12, 16, 20];
let totalDist = 0;
fingerTips.forEach(idx => {
const tip = landmarks[idx];
const dist = Math.sqrt(
Math.pow(tip.x - wrist.x, 2) +
Math.pow(tip.y - wrist.y, 2)
);
totalDist += dist;
});
const avgDist = totalDist / 5;
// 经验阈值:
// 握拳时,指尖距离手腕很近 (avgDist 约 0.1 - 0.2)
// 张开时,指尖距离手腕较远 (avgDist 约 0.4 - 0.6)
// 我们做一个映射:握拳(distance small) -> 聚合(factor 1), 张开 -> 散开(factor 0)
// 动态调整这些阈值以适应摄像头的距离
const closeThreshold = 0.25;
const openThreshold = 0.5;
let normalized = (avgDist - closeThreshold) / (openThreshold - closeThreshold);
normalized = 1 - Math.min(Math.max(normalized, 0), 1); // 反转:距离越小(握拳),值越大(1)
targetGatherFactor = normalized;
} else {
// 如果没有检测到手,默认缓慢散开
targetGatherFactor = 0;
}
}
const hands = new Hands({locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;
}});
hands.setOptions({
maxNumHands: 1,
modelComplexity: 1,
minDetectionConfidence: 0.5,
minTrackingConfidence: 0.5
});
hands.onResults(onResults);
const cameraUtils = new Camera(videoElement, {
onFrame: async () => {
await hands.send({image: videoElement});
},
width: 640,
height: 480
});
cameraUtils.start();
// --- 5. 动画循环 ---
function animate() {
requestAnimationFrame(animate);
// 粒子自身旋转动画
particles.rotation.y += 0.002;
// 平滑过渡聚合系数 (Lerp)
gatherFactor += (targetGatherFactor - gatherFactor) * 0.05;
// 更新粒子位置
const posAttr = particles.geometry.attributes.position;
const currentPositions = posAttr.array;
for (let i = 0; i < particleCount; i++) {
const idx = i * 3;
// 目标位置插值:从 randomPositions 过渡到 targetPositions
const tx = randomPositions[idx] + (targetPositions[idx] - randomPositions[idx]) * gatherFactor;
const ty = randomPositions[idx+1] + (targetPositions[idx+1] - randomPositions[idx+1]) * gatherFactor;
const tz = randomPositions[idx+2] + (targetPositions[idx+2] - randomPositions[idx+2]) * gatherFactor;
// 增加一点动态浮动效果
currentPositions[idx] += (tx - currentPositions[idx]) * 0.1;
currentPositions[idx+1] += (ty - currentPositions[idx+1]) * 0.1;
currentPositions[idx+2] += (tz - currentPositions[idx+2]) * 0.1;
}
posAttr.needsUpdate = true;
renderer.render(scene, camera);
}
animate();
// 窗口大小调整适配
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>
🌟 项目简介 | Project Introduction
这是一个基于 Three.js 和 MediaPipe Hands 的前端交互项目。通过摄像头捕捉你的手势,你可以控制屏幕上的数千个爱心粒子聚拢或散开。握拳时爱心会聚拢成形,张开手掌时则会散落如星辰。是不是超级浪漫又有科技感呢?🥰
📌 前提条件 | Prerequisites
- 现代浏览器: Chrome/Firefox/Safari 最新版
- 摄像头设备: 内置或外接摄像头
- 基础 HTML/CSS/JS 知识: 有助于理解代码结构
🚀 核心技术栈 | Core Technologies
| 技术 | 用途 | 链接 |
|---|---|---|
| Three.js | WebGL 3D 渲染引擎 | threejs.org |
| MediaPipe | 手部关键点识别 | mediapipe.dev |
| HTML5 Canvas | 粒子渲染容器 | - |
💖 爱心数学公式 | Heart Math Formula
我们使用经典的爱心参数方程来生成粒子目标位置:
javascript
function getHeartPosition(t, scale = 1) {
const x = 16 * Math.pow(Math.sin(t), 3);
const y = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t);
return { x: x * scale, y: y * scale, z: 0 };
}
这个方程可以绘制出完美的爱心轮廓,再配合粒子系统就能创造出梦幻的效果啦!💗
👐 手势识别原理 | Gesture Recognition Principle
通过 MediaPipe 获取手部 21 个关键点坐标,我们重点分析以下关键点:
- 手腕(Wrist): 索引 0
- 指尖(Finger Tips): 索引 4(拇指), 8(食指), 12(中指), 16(无名指), 20(小指)
计算各指尖到手腕的平均距离,以此判断握拳(爱心聚拢)或张开(爱心散开)状态。
🧩 核心代码片段 | Core Code Snippets
1. 粒子系统初始化 | Particle System Initialization
javascript
const particleCount = 3000;
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const targetPositions = new Float32Array(particleCount * 3);
const randomPositions = new Float32Array(particleCount * 3);
// 初始化爱心目标位置和随机初始位置...
2. 手势控制逻辑 | Gesture Control Logic
javascript
function onResults(results) {
if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
const landmarks = results.multiHandLandmarks[0];
// 计算平均指尖距离并映射到聚合因子...
} else {
targetGatherFactor = 0; // 默认散开
}
}
3. 粒子动画循环 | Animation Loop
javascript
function animate() {
requestAnimationFrame(animate);
// 平滑过渡聚合系数
gatherFactor += (targetGatherFactor - gatherFactor) * 0.05;
// 更新每个粒子位置(从随机位置插值到爱心形状)
for (let i = 0; i < particleCount; i++) {
// 插值计算...
}
renderer.render(scene, camera);
}
🎨 视觉效果亮点 | Visual Highlights
- Additive Blending 加法混合: 粒子重叠时颜色叠加,营造梦幻光晕效果
- Smooth Lerp 平滑插值: 粒子移动自然流畅,无突兀感
- Dynamic Rotation 动态旋转: 整体粒子云缓慢自旋,增强视觉动感
- Responsive Design 响应式设计: 自动适配不同屏幕尺寸
🛠️ 使用指南 | Run Guide
本地运行 | Local Run
- 将代码保存为 heart.html
- 用现代浏览器直接打开即可(需允许摄像头权限)
🔧 定制项 | Customization Options
| 项目 | 修改方法 | 效果预览 |
|---|---|---|
| 粒子颜色 | 更改 color: 0xff69b4 |
💗 粉色爱心 → 💙 蓝色星辰 |
| 粒子大小 | 调整 size: 0.4 |
🌟 大颗粒 → ⭐ 小星星 |
| 粒子数量 | 修改 particleCount |
🌌 稀疏星空 → 💫 浓密银河 |
| 形状变换 | 替换爱心方程 | ❤️ 爱心 → 🌸 樱花 |
🐛 常见问题 | Troubleshooting
-
摄像头无法启动?
- 检查浏览器权限设置
- 确保没有其他程序占用摄像头
-
手势识别不准确?
- 保持手部在摄像头清晰可见范围内
- 调整环境光线避免过暗或过曝
-
性能卡顿怎么办?
- 减少粒子数量 (
particleCount) - 关闭其他占用资源的程序
- 减少粒子数量 (
📚 扩展学习资源 | Extended Resources
Conclusion | 结语
-
That's all for today~ - | 今天就写到这里啦~
-
Guys, ( ̄ω ̄( ̄ω ̄〃 ( ̄ω ̄〃)ゝ See you tomorrow~~ | 小伙伴们,( ̄ω ̄( ̄ω ̄〃 ( ̄ω ̄〃)ゝ我们明天再见啦~~
-
Everyone, be happy every day! 大家要天天开心哦
-
Welcome everyone to point out any mistakes in the article~ | 欢迎大家指出文章需要改正之处~
-
Learning has no end; win-win cooperation | 学无止境,合作共赢
-
Welcome all the passers-by, boys and girls, to offer better suggestions! ~~~ | 欢迎路过的小哥哥小姐姐们提出更好的意见哇~~
