用 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

相关推荐
@ 前端小白10 分钟前
封装倒计时自定义react hook
前端·javascript·react.js
咔咔库奇13 分钟前
【react】Redux的设计思想与工作原理
前端·react.js·前端框架
码农君莫笑16 分钟前
在 Blazor 和 ASP.NET Core 中使用依赖注入和Scoped 服务实现数据共享方法详解
前端·后端·c#·.netcore·visual studio
Catherinemin41 分钟前
HTML5新特性|05 CSS3边框&CSS3背景
前端·css3
JINGWHALE142 分钟前
设计模式 结构型 装饰器模式(Decorator Pattern)与 常见技术框架应用 解析
前端·人工智能·后端·设计模式·性能优化·系统架构·装饰器模式
李是啥也不会44 分钟前
番外篇-CSS3新增特性
前端·css·css3
CodeClimb1 小时前
【华为OD-E卷 - 最优资源分配 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
16年上任的CTO1 小时前
一文大白话讲清楚CSS性能优化
前端·javascript·css·性能优化·css性能优化
jjw_zyfx1 小时前
vue3 css实现文字输出带光标显示,文字输出完毕,光标消失的效果
前端·javascript·css
球球不吃虾1 小时前
VuePress2配置unocss的闭坑指南
前端·javascript