提前祝福大家新年快乐,今天用一个新年快乐的教程来结束这一年。
看下效果
在这个案例中,我们使用了 vue3 ,有一个浮动的新年快乐的字体,然后有一堆从下到上的小粒子,在文字背后有一个模拟烟花绽放的效果。
环境搭建
我们使用 vite 来搭建一个 vue3 的项目,并且安装 three 的依赖。
通过下面的命令来创建一个 vite 项目
bash
npm create vite
根据提示选择 vue 3 项目,然后安装 three.js
bash
npm install three
运行项目
bash
npm run dev
目录创建
├── src/
│ ├── components/
│ │ └── ThreeScene.vue # 主要的 3D 场景组件
│ ├── utils/
│ │ └── fireworks.js # 烟花效果管理
│ ├── App.vue # 根组件
│ └── main.js # 应用入口
└── index.html
其中 ThreeScene.vue
是主要的 3D 场景组件,fireworks.js
是烟花效果的管理。App.vue 是根组件,main.js 是应用入口。
ThreeScene.vue 核心代码解析
这段代码通过 Vue 3 和 Three.js 创建了一个具有动态效果的 3D 场景,包括:
- 一个 3D 的 "新年快乐" 文字效果。
- 色彩斑斓的粒子效果,模拟烟花。
- 每个元素(文字、粒子和烟花)在动画循环中动态更新。
1. 模板部分 (<template>
)
这个部分定义了一个 div
容器,ref="container"
用来在脚本中访问该元素,将 Three.js 渲染的 3D 内容添加到这个容器中。
```javascript
```
2. 脚本部分 (<script setup>
)
引入依赖
javascript
import { ref, onMounted, onBeforeUnmount } from "vue";
import * as THREE from "three";
import { FireworksManager } from "../utils/fireworks";
import { FontLoader } from "three/examples/jsm/loaders/FontLoader";
import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry";
ref
:Vue 3 的响应式引用,用于访问 DOM 元素。onMounted
和onBeforeUnmount
:Vue 生命周期钩子,用于在组件挂载后和卸载前执行代码。THREE
:引入 Three.js 进行 3D 渲染。FireworksManager
:自定义的烟花效果管理器。FontLoader
和TextGeometry
:用于加载和渲染 3D 字体。
变量声明
js
const container = ref(null);
let scene, camera, renderer, fireworksManager;
let textMesh;
let particleSystem, particleMaterial, particleGeometry;
let animationFrameId;
let particleColors;
scene
:Three.js 场景对象。camera
:摄像机对象。renderer
:渲染器对象。fireworksManager
:烟花效果管理器。textMesh
:存储 3D 字体的 Mesh 对象。particleSystem
,particleMaterial
,particleGeometry
:存储粒子系统相关的对象。animationFrameId
:用于存储动画帧 ID,方便控制动画的停止。particleColors
:存储粒子颜色数据。
初始化函数(init
)
初始化 Three.js 的核心组件,包括场景、相机、渲染器、光源、烟花效果以及粒子系统。
javascript
const init = () => {
// 初始化场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
// 初始化相机
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 20, 100);
camera.lookAt(0, 0, 0);
// 初始化渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
container.value.appendChild(renderer.domElement);
// 添加环境光和方向光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 5, 5);
scene.add(directionalLight);
// 初始化烟花效果
fireworksManager = new FireworksManager(scene);
// 添加3D文字
addText();
// 添加五颜六色的粒子效果
addParticles();
// 绑定窗口调整事件
window.addEventListener("resize", onWindowResize);
};
scene
:创建一个 Three.js 场景对象,并设置背景为黑色。camera
:创建一个透视相机,设置相机的视角和位置。renderer
:创建一个 WebGL 渲染器并添加到页面容器中。ambientLight
和directionalLight
:添加环境光和方向光,以增加场景的亮度。fireworksManager
:初始化烟花效果管理器。addText
和addParticles
:调用函数分别添加 3D 文本和粒子系统。onWindowResize
:监听页面的窗口大小变化,调整渲染器和相机。
3D文字添加 (addText
)
javascript
const addText = () => {
const loader = new FontLoader();
loader.load("/fonts/FangSong_Regular.json", (font) => {
const textGeometry = new TextGeometry("新年快乐", {
font: font,
size: 14,
height: 3,
curveSegments: 36,
bevelEnabled: true,
bevelThickness: 0.4,
bevelSize: 0.3,
bevelSegments: 10,
});
...
});
};
粒子系统添加(addParticles
)
生成一个粒子系统,每个粒子具有不同的颜色和随机位置,粒子系统被添加到场景中。
javascript
const addParticles = () => {
particleGeometry = new THREE.BufferGeometry();
const particleCount = 1000;
const positions = [];
particleColors = [];
for (let i = 0; i < particleCount; i++) {
positions.push((Math.random() - 0.5) * 200, Math.random() * 100, (Math.random() - 0.5) * 200);
const color = new THREE.Color(Math.random(), Math.random(), Math.random());
particleColors.push(color.r, color.g, color.b);
}
...
};
动画函数 (animate
)
这个函数用于不断更新场景中的元素,并执行渲染操作。
javascript
const animate = () => {
animationFrameId = requestAnimationFrame(animate);
if (Math.random() < 0.08) {
fireworksManager.addFirework();
}
fireworksManager.update();
...
renderer.render(scene, camera);
};
窗口调整事件 (onWindowResize
)
监听页面的窗口尺寸变化,动态调整渲染器的大小和相机的视角。
javascript
const onWindowResize = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
};
生命周期钩子
在 onMounted
钩子中,初始化 Three.js 场景,并开始动画。并在 onBeforeUnmount
钩子中,清理资源,取消动画帧请求和移除事件监听器。
javascript
onMounted(() => {
init();
animate();
});
onBeforeUnmount(() => {
cancelAnimationFrame(animationFrameId);
window.removeEventListener("resize", onWindowResize);
if (container.value) {
container.value.removeChild(renderer.domElement);
}
scene.dispose();
particleGeometry.dispose();
particleMaterial.dispose();
fireworksManager.dispose();
});
样式部分 (<style scoped>
)
css
.scene-container {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
overflow: hidden;
}
.scene-container
样式设置了容器的宽度和高度为全屏,并确保其位置固定,不会因滚动条而变化,同时隐藏溢出的内容。
fireworks.js 核心代码解析
这段代码的核心是创建并管理多个烟花实例,烟花分为上升阶段和爆炸阶段。通过 Firework
类管理每个烟花的状态、粒子效果及其更新逻辑。FireworksManager
类用于管理多个烟花实例,并在每一帧更新它们的状态。
1. getRandomColor()
- 随机生成颜色
javascript
getRandomColor() {
const colors = [
new THREE.Color(0xff0000), // 红色
new THREE.Color(0x00ff00), // 绿色
new THREE.Color(0x0000ff), // 蓝色
// 更多颜色...
];
return colors[Math.floor(Math.random() * colors.length)];
}
2. createTrail()
- 创建烟花上升阶段的拖尾效果
javascript
createTrail() {
const trailGeometry = new THREE.SphereGeometry(0.2, 8, 8);
const trailMaterial = new THREE.MeshBasicMaterial({
color: this.mesh.material.color,
transparent: true,
opacity: 0.6,
});
const trail = new THREE.Mesh(trailGeometry, trailMaterial);
trail.position.copy(this.mesh.position);
trail.userData.life = 0.5;
this.trails.push(trail);
this.scene.add(trail);
}
3. createExplosionPattern()
- 创建爆炸模式
根据爆炸的不同模式(球形、环形、柳树形等),计算每个粒子的速度方向,并生成爆炸效果。
javascript
createExplosionPattern(baseVelocity, count, pattern) {
const velocities = [];
switch (pattern) {
case "sphere":
// 球形爆炸模式
for (let i = 0; i < count; i++) {
const phi = Math.acos(-1 + (2 * i) / count);
const theta = Math.sqrt(count * Math.PI) * phi;
const velocity = new THREE.Vector3();
velocity.x = Math.cos(theta) * Math.sin(phi);
velocity.y = Math.sin(theta) * Math.sin(phi);
velocity.z = Math.cos(phi);
velocity.multiplyScalar(baseVelocity * (1 + Math.random()));
velocities.push(velocity);
}
break;
// 其他模式的处理
}
return velocities;
}
4. explode()
- 烟花爆炸效果
javascript
explode() {
const baseColor = this.getRandomColor();
// 初始化粒子位置
const positions = this.particlePositions;
this.particleVelocities = [];
for (let i = 0; i < this.particleCount; i++) {
// 设置粒子位置和速度
positions[i3] = this.mesh.position.x;
positions[i3 + 1] = this.mesh.position.y;
positions[i3 + 2] = this.mesh.position.z;
// 更多粒子计算...
}
// 创建粒子系统
const particleMaterial = new THREE.PointsMaterial({
size: 0.3,
color: baseColor,
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending,
sizeAttenuation: true,
vertexColors: true,
});
this.particles = new THREE.Points(this.particleGeometry, particleMaterial);
this.scene.add(this.particles);
this.scene.remove(this.mesh); // 移除烟花球体
this.isExploded = true;
}
5. update()
- 更新烟花状态
该方法用于更新烟花的上升阶段和爆炸阶段,处理烟花的运动、拖尾、爆炸等效果。
javascript
update() {
if (!this.isExploded) {
// 上升阶段的更新
this.mesh.position.add(this.velocity);
this.velocity.y -= 0.15; // 重力
if (Math.random() < 0.3) {
this.createTrail();
}
// 更新拖尾
for (let i = this.trails.length - 1; i >= 0; i--) {
const trail = this.trails[i];
trail.userData.life -= 0.05;
trail.material.opacity = trail.userData.life;
if (trail.userData.life <= 0) {
this.scene.remove(trail);
this.trails.splice(i, 1);
}
}
if (this.mesh.position.y < 20 && this.velocity.y < 0) {
this.explode(); // 爆炸判断
}
} else if (this.particles) {
// 爆炸阶段的更新
const positions = this.particleGeometry.attributes.position.array;
let alive = false;
for (let i = 0; i < this.particleCount; i++) {
// 更新粒子位置
positions[i3] += velocity.x;
positions[i3 + 1] += velocity.y;
positions[i3 + 2] += velocity.z;
}
// 消失效果
this.particles.material.opacity *= 0.985;
this.particles.material.size *= 0.99;
this.particleGeometry.attributes.position.needsUpdate = true;
if (this.particles.material.opacity > 0.01) {
alive = true;
}
if (!alive) {
this.scene.remove(this.particles);
this.particles = null;
}
return alive;
}
return this.trails.length > 0 || !this.isExploded;
}
总结
基于 Vue 3 和 Three.js 的互动 3D 场景,结合了烟花效果、粒子系统和 3D 文字。首先,初始化了一个 Three.js 场景、相机和渲染器,并添加了基础的光源。
通过 FireworksManager 管理烟花效果,随机生成并更新烟花。代码使用 FontLoader 加载字体并创建 3D 文字,并通过 TextGeometry 渲染带有发光效果的文字,添加到场景中。
粒子效果通过 BufferGeometry 管理粒子的位置和颜色,实现了随机分布的彩色粒子上升效果。
在动画部分,文字上下浮动,粒子随着时间上升并重置,增强了动态效果。
同时,绑定了窗口调整事件,确保在窗口大小变化时重新计算场景尺寸。
组件销毁时,通过 onBeforeUnmount 清理资源,移除事件监听器并释放内存,避免潜在的内存泄漏。
代码
github
https://github.com/calmound/threejs-demo/tree/main/happy