用 vue3 实现新年快乐

提前祝福大家新年快乐,今天用一个新年快乐的教程来结束这一年。

看下效果

在这个案例中,我们使用了 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 元素。
  • onMountedonBeforeUnmount:Vue 生命周期钩子,用于在组件挂载后和卸载前执行代码。
  • THREE:引入 Three.js 进行 3D 渲染。
  • FireworksManager:自定义的烟花效果管理器。
  • FontLoaderTextGeometry:用于加载和渲染 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 渲染器并添加到页面容器中。
  • ambientLightdirectionalLight:添加环境光和方向光,以增加场景的亮度。
  • fireworksManager:初始化烟花效果管理器。
  • addTextaddParticles:调用函数分别添加 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

gitee

https://gitee.com/calmound/threejs-demo/tree/main/happy

相关推荐
小小小小宇16 小时前
Claude Code 自动运行方法大全
前端
道友可好16 小时前
AI 测试全绿,代码却是错的
前端·人工智能·后端
国科安芯16 小时前
商业航天通信载荷数字处理单元供电架构研究——基于ASP7A84AS的高精度低压差线性稳压器技术分析
前端·单片机·嵌入式硬件·fpga开发·架构·安全性测试
TangentDomain16 小时前
AI 写代码时代,游戏 UI 架构为什么停在 MVP?
前端·游戏·架构
英勇无比的消炎药16 小时前
前端提效神器全新AI组件库TinyRobot改写日常开发模式
前端·vue.js
GuWenyue16 小时前
10分钟搞定TodoList实战!从0搭建Bun+TS的RESTful接口服务
前端·typescript·bun
IMPYLH17 小时前
HTML 的 <a>元素
前端·javascript·html
PedroQue9917 小时前
uni-router:uni-app路由管理新选择
前端·uni-app
Cerrda17 小时前
一行指令搞定复制:Vue 3 vCopy 实现解析
前端·代码规范
ZengLiangYi17 小时前
本地向量数据库选型:vectra vs chroma vs hnswlib
javascript·数据库·后端