使用 Three.js 和 GSAP 动画库实现3D 名字抽奖

通过 Three.js 和 GSAP 的结合使用,创造一个引人入胜的 3D 抽奖体验,特别是中奖结果展示环节的动画效果,使整个抽奖过程更加令人期待。

核心技术实现

一、 Three.js 3D 场景搭建

javascript 复制代码
// 创建场景、相机和渲染器
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000); // 黑色背景

// 创建透视相机
camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 1000);
camera.position.z = 500;

// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width, height);
container.appendChild(renderer.domElement);

// 添加灯光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
directionalLight.position.set(0, 1, 1);
scene.add(directionalLight);

在 Three.js 中,场景(Scene)相机(Camera)渲染器(Renderer) 是构建 3D 场景的三大核心组件,它们共同协作完成 3D 画面的呈现。可以类比现实世界中 "拍摄电影" 的过程来理解:

1. 场景(Scene)

  • 概念:场景是 3D 物体、灯光、相机等所有元素的 "容器",相当于现实世界中的 "拍摄现场"。

  • 作用

    • 组织和管理所有 3D 对象(如网格模型、粒子系统等)。
    • 定义物体之间的层级关系(父子关系)。
    • 决定哪些物体对光线有反应(受光照影响)。
js 复制代码
import * as THREE from 'three';
const scene = new THREE.Scene(); // 创建场景

// 向场景中添加物体(如一个立方体)
const geometry = new THREE.BoxGeometry(1, 1, 1); // 几何体
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); // 材质
const cube = new THREE.Mesh(geometry, material); // 网格模型(几何体+材质)
scene.add(cube); // 将模型添加到场景

2. 相机(Camera)

  • 概念:相机决定了从哪个角度和视角 "观察" 场景中的物体,相当于现实世界中的 "摄像机"。

  • 作用

    • 定义观察点(位置)和观察方向。
    • 控制视野范围(类似相机的镜头焦距)。
    • 决定 3D 场景如何投影到 2D 屏幕上(透视投影 / 正交投影)。

常见类型

  • 透视相机(PerspectiveCamera) :模拟人眼视角,近大远小,有真实的深度感(适合大多数 3D 场景)。
js 复制代码
// 参数:视野角度(fov)、宽高比(aspect)、近裁剪面(near)、远裁剪面(far)
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5; // 相机位置(沿 z 轴远离原点)
  • 正交相机(OrthographicCamera) :物体大小不受距离影响,无透视效果(适合 2D 游戏、工程绘图等)。

  • 核心参数

    • position:相机在 3D 空间中的位置(x, y, z)。
    • lookAt(target):设置相机的观察目标(如指向场景原点 scene.position)。

3. 渲染器(Renderer)

  • 概念:渲染器是将 "场景" 通过 "相机" 的视角渲染到屏幕上的工具,相当于现实世界中的 "显示器" 或 "胶片"。

  • 作用

    • 将 3D 场景转换为 2D 像素图像并绘制到 <canvas> 元素上。
    • 处理光照、阴影、材质等视觉效果的计算。
    • 控制渲染性能(如帧率、抗锯齿等)。
js 复制代码
// 创建渲染器,绑定到一个 canvas 元素(或自动创建)
const renderer = new THREE.WebGLRenderer({ antialias: true }); // 开启抗锯齿
renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染尺寸(全屏)
document.body.appendChild(renderer.domElement); // 将渲染器的 canvas 添加到页面

// 渲染场景(需要在动画循环中持续调用)
function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera); // 核心:用相机拍摄场景并渲染
}
animate();

三者关系总结

  1. 场景:存放所有 3D 物体和环境元素。
  2. 相机:定义 "看哪里" 和 "怎么看"。
  3. 渲染器:执行 "拍摄" 动作,将相机看到的场景绘制到屏幕上。

用一句话概括:渲染器将相机所 "看到" 的场景内容,实时绘制到网页的 canvas 上,形成我们最终看到的 3D 画面。这三者是 Three.js 所有 3D 应用的基础,缺一不可。

二、创建参与者头像和名称对象

javascript 复制代码
// 为每个参与者创建平面几何体,并将头像作为纹理
const material = new THREE.MeshBasicMaterial({ 
  map: texture, 
  side: THREE.DoubleSide
});

const geometry = new THREE.PlaneGeometry(60, 60);
const nameObject = new THREE.Mesh(geometry, material);

// 均匀分布在球面上
const phi = Math.acos(-1 + (2 * index) / participants.length);
const theta = Math.sqrt(participants.length * Math.PI) * phi;

nameObject.position.x = radius * Math.sin(phi) * Math.cos(theta);
nameObject.position.y = radius * Math.sin(phi) * Math.sin(theta);
nameObject.position.z = radius * Math.cos(phi);

// 让对象始终面向相机
nameObject.lookAt(camera.position);

三、使用 Canvas 创建头像纹理

javascript 复制代码
// 使用 Canvas 创建参与者头像纹理
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');

// 绘制圆形头像
context.save();
context.beginPath();
context.arc(128, 100, 80, 0, Math.PI * 2);
context.closePath();
context.clip();
context.drawImage(img, 48, 20, 160, 160);

// 绘制名称
context.fillStyle = '#ffffff';
context.font = 'bold 24px Arial';
context.textAlign = 'center';
context.fillText(participant.name, 128, 220);

// 创建纹理
const texture = new THREE.CanvasTexture(canvas);

四、动画循环与场景旋转

javascript 复制代码
// 动画循环
const animate = () => {
  animationFrameId = requestAnimationFrame(animate);
  
  if (props.isSpinning) {
    // 旋转整个场景
    scene.rotation.y += rotationSpeed.value;
    scene.rotation.x += rotationSpeed.value / 2;
  }
  
  // 中奖者卡片旋转
  if (isHighlightingWinner.value && winnerObject.value) {
    winnerObject.value.rotation.y += 0.03;
  }
  
  renderer.render(scene, camera);
};

// 抽奖停止时的减速效果
const slowDown = () => {
  rotationSpeed.value *= 0.95;
  
  if (rotationSpeed.value > 0.001) {
    setTimeout(slowDown, 100);
  } else {
    rotationSpeed.value = 0;
    // 选择并突出显示获奖者
    const winner = selectWinner();
    highlightWinner(winner);
  }
};

五、使用 GSAP 实现中奖者突出效果

javascript 复制代码
// 重置场景旋转
gsap.to(scene.rotation, {
  x: 0,
  y: 0,
  z: 0,
  duration: 1.0,
  ease: "power2.out"
});

// 淡出其他参与者
gsap.to(obj.material, { 
  opacity: 0, 
  duration: 1.0,
  onStart: () => {
    obj.material.transparent = true;
  }
});

// 将中奖者移至中央并放大
gsap.to(winnerObj.position, { 
  x: 0, 
  y: 0, 
  z: 200, 
  duration: 1.5,
  ease: "power2.out"
});

gsap.to(winnerObj.scale, { 
  x: 2.5, 
  y: 2.5, 
  z: 2.5, 
  duration: 1.5,
  ease: "power2.out"
});

六、光晕效果实现

javascript 复制代码
// 创建光晕效果
const glowGeometry = new THREE.PlaneGeometry(70, 70);
const glowMaterial = new THREE.MeshBasicMaterial({
  color: 0xffff00,
  transparent: true,
  opacity: 0.3,
  side: THREE.DoubleSide
});

glowMesh = new THREE.Mesh(glowGeometry, glowMaterial);
glowMesh.position.z = obj.position.z - 1; // 放在对象后面

// 创建光晕动画
gsap.to(glowMaterial, {
  opacity: 0.7,
  duration: 1,
  repeat: -1,
  yoyo: true,
  ease: "sine.inOut"
});

七、场景重置与清理

javascript 复制代码
// 停止所有GSAP动画
gsap.killTweensOf(scene.rotation);
nameObjects.forEach(obj => {
  gsap.killTweensOf(obj.position);
  gsap.killTweensOf(obj.scale);
  gsap.killTweensOf(obj.material);
});

// 清除光晕对象
if (glowMesh && scene) {
  scene.remove(glowMesh);
  glowMesh = null;
}

// 重新创建所有对象
createNameObjects();

实现要点与技巧

  1. 空间分布算法:使用球面坐标系均匀分布参与者对象,确保无论参与人数多少都能均匀分布。

  2. 头像加载容错:实现了多级容错机制,当远程头像加载失败时,会自动创建带有首字母的默认头像。

  3. 动画过渡:使用 GSAP 实现平滑的动画过渡,包括旋转减速、对象移动和透明度变化。

  4. 性能优化

    • 使用 requestAnimationFrame 进行动画循环
    • 在组件卸载时清理资源和事件监听
    • 使用 killTweensOf 停止不需要的动画
  5. 响应式设计:监听窗口大小变化,动态调整渲染尺寸。

  6. 中奖者突出效果

    • 场景旋转重置到正面
    • 其他参与者淡出并远离
    • 中奖者移至中央并放大
    • 添加光晕效果和旋转动画
    • 显示中奖者名称

优势与特点

  1. 视觉吸引力:3D 效果比传统 2D 抽奖更具视觉冲击力
  2. 沉浸式体验:通过空间分布和动画效果增强用户体验
  3. 可定制性:可以轻松调整颜色、大小、动画速度等参数
  4. 性能良好:使用 Three.js 硬件加速渲染,即使大量参与者也能保持流畅
相关推荐
向葭奔赴♡9 小时前
HTML的本质——网页的“骨架”
前端·javascript·html
清欢ysy9 小时前
Cannot find module ‘@next/bundle-analyzer‘
开发语言·javascript·arcgis
小岛前端9 小时前
Vue3 键盘快捷键的高效开发!
前端·vue.js·开源
江城开朗的豌豆9 小时前
小程序避坑指南:这些兼容性问题你遇到了几个?
前端·javascript·微信小程序
云浪9 小时前
说透 Suspense 组件的实现原理
前端·javascript·vue.js
江城开朗的豌豆9 小时前
玩转小程序页面跳转:我的路由实战笔记
前端·javascript·微信小程序
前端 贾公子9 小时前
Vue 响应式高阶 API - effectScope
前端·javascript·vue.js
^O^ ^O^10 小时前
pc端pdf预览
前端·javascript·pdf
艾小码10 小时前
还在纠结用v-if还是v-show?看完这篇彻底搞懂Vue渲染机制!
javascript·vue.js