引言

在现代Web开发中,3D图形和动画的应用越来越广泛,从游戏到数据可视化,再到虚拟现实,3D技术为用户带来了前所未有的沉浸式体验。Vue.js作为前端框架的佼佼者,以其简洁的API和强大的响应式系统备受开发者青睐。而Three.js则是基于WebGL的3D图形库,简化了复杂的3D渲染过程。将Vue与Three.js结合,可以构建出高性能且易于维护的3D应用。本文将详细介绍如何使用Vue 3、TypeScript和Three.js来封装一个可重用的3D动画框架。

1. 技术栈介绍

1.1 Vue 3

Vue 3是Vue框架的最新版本,带来了许多改进:

  • 性能提升:更快的虚拟DOM算法。
  • 简洁API:Composition API使得代码更加模块化。
  • 响应性系统:新的Proxy-based响应性系统,支持更细粒度的更新。

1.2 TypeScript

TypeScript是JavaScript的超集,提供了静态类型检查和编译时错误检测:

  • 类型安全:减少运行时错误。
  • 代码维护:大型项目更易于管理和维护。
  • 开发效率:IDE智能提示,提升编码速度。

1.3 Three.js

Three.js是基于WebGL的3D图形库,简化了3D渲染过程:

  • 易于使用:提供高层次的API,无需深入了解WebGL。
  • 功能丰富:支持多种3D模型、材质、光源和动画。
  • 社区支持:活跃的社区和丰富的文档。

2. 开发环境准备

在开始之前,确保你已经安装了以下软件:

  • Node.js:推荐使用最新的LTS版本。
  • npm或yarn:包管理工具。

2.1 安装Vue CLI

首先,全局安装Vue CLI:

npm install -g @vue/cli

2.2 初始化Vue 3项目

创建一个新的Vue 3项目:

vue create my-project
cd my-project

在选择预设时,选择使用TypeScript和Vue 3的选项。

2.3 安装Three.js

在项目根目录下安装Three.js:

npm install three

3. 创建基本的3D组件

我们将从创建一个基础的3D组件开始,这个组件可以作为所有3D元素的基础。

3.1 创建Vue组件

src/components目录下创建一个名为ThreeScene.vue的文件:

<template>
  <div ref="threeContainer" class="three-container"></div>
</template>

<script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
import * as THREE from 'three';

export default defineComponent({
  name: 'ThreeScene',
  setup() {
    const threeContainer = ref<HTMLDivElement | null>(null);
    let scene: THREE.Scene;
    let camera: THREE.PerspectiveCamera;
    let renderer: THREE.WebGLRenderer;

    const initThree = () => {
      if (!threeContainer.value) return;

      // 创建场景
      scene = new THREE.Scene();

      // 创建相机
      camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
      camera.position.z = 5;

      // 创建渲染器
      renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);
      threeContainer.value.appendChild(renderer.domElement);

      // 添加一个立方体
      const geometry = new THREE.BoxGeometry();
      const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
      const cube = new THREE.Mesh(geometry, material);
      scene.add(cube);

      // 动画循环
      const animate = () => {
        requestAnimationFrame(animate);
        cube.rotation.x += 0.01;
        cube.rotation.y += 0.01;
        renderer.render(scene, camera);
      };

      animate();
    };

    const handleResize = () => {
      if (camera && renderer) {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
      }
    };

    onMounted(() => {
      initThree();
      window.addEventListener('resize', handleResize);
    });

    onUnmounted(() => {
      window.removeEventListener('resize', handleResize);
    });

    return {
      threeContainer,
    };
  },
});
</script>

<style scoped>
.three-container {
  width: 100%;
  height: 100vh;
}
</style>

4. 加载3D模型

在Vue组件中使用Three.js加载3D模型,可以提升场景的真实感和交互性。

4.1 安装GLTFLoader

首先,安装GLTFLoader模块:

npm install three/examples/jsm/loaders/GLTFLoader

4.2 修改ThreeScene组件

ThreeScene.vue中引入GLTFLoader,并加载glTF格式的3D模型:

<script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

export default defineComponent({
  name: 'ThreeScene',
  setup() {
    const threeContainer = ref<HTMLDivElement | null>(null);
    let scene: THREE.Scene;
    let camera: THREE.PerspectiveCamera;
    let renderer: THREE.WebGLRenderer;
    let model: THREE.Group;

    const initThree = () => {
      if (!threeContainer.value) return;

      // 创建场景
      scene = new THREE.Scene();

      // 创建相机
      camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
      camera.position.z = 5;

      // 创建渲染器
      renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);
      threeContainer.value.appendChild(renderer.domElement);

      // 加载3D模型
      const loader = new GLTFLoader();
      loader.load('path/to/your/model.glb', (gltf) => {
        model = gltf.scene;
        scene.add(model);
      });

      // 添加光源
      const light = new THREE.PointLight(0xffffff, 1, 100);
      light.position.set(10, 10, 10);
      scene.add(light);

      // 动画循环
      const animate = () => {
        requestAnimationFrame(animate);
        if (model) {
          model.rotation.y += 0.01;
        }
        renderer.render(scene, camera);
      };

      animate();
    };

    const handleResize = () => {
      if (camera && renderer) {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
      }
    };

    onMounted(() => {
      initThree();
      window.addEventListener('resize', handleResize);
    });

    onUnmounted(() => {
      window.removeEventListener('resize', handleResize);
    });

    return {
      threeContainer,
    };
  },
});
</script>

5. 添加交互功能

为了让3D场景更加真实和交互性更强,我们可以添加一些交互功能,如拖拽缩放控制器和地平线网格。

5.1 安装OrbitControls

首先,安装OrbitControls模块:

npm install three/examples/jsm/controls/OrbitControls

5.2 修改ThreeScene组件

ThreeScene.vue中引入OrbitControls,并添加地平线网格:

<script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

export default defineComponent({
  name: 'ThreeScene',
  setup() {
    const threeContainer = ref<HTMLDivElement | null>(null);
    let scene: THREE.Scene;
    let camera: THREE.PerspectiveCamera;
    let renderer: THREE.WebGLRenderer;
    let model: THREE.Group;
    let controls: OrbitControls;

    const initThree = () => {
      if (!threeContainer.value) return;

      // 创建场景
      scene = new THREE.Scene();

      // 创建相机
      camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
      camera.position.z = 5;

      // 创建渲染器
      renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);
      threeContainer.value.appendChild(renderer.domElement);

      // 加载3D模型
      const loader = new GLTFLoader();
      loader.load('path/to/your/model.glb', (gltf) => {
        model = gltf.scene;
        scene.add(model);
      });

      // 添加光源
      const light = new THREE.PointLight(0xffffff, 1, 100);
      light.position.set(10, 10, 10);
      scene.add(light);

      // 添加地平线网格
      const gridHelper = new THREE.GridHelper(10, 10);
      scene.add(gridHelper);

      // 添加控制器
      controls = new OrbitControls(camera, renderer.domElement);
      controls.enableDamping = true;

      // 动画循环
      const animate = () => {
        requestAnimationFrame(animate);
        controls.update();
        renderer.render(scene, camera);
      };

      animate();
    };

    const handleResize = () => {
      if (camera && renderer) {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
      }
    };

    onMounted(() => {
      initThree();
      window.addEventListener('resize', handleResize);
    });

    onUnmounted(() => {
      window.removeEventListener('resize', handleResize);
    });

    return {
      threeContainer,
    };
  },
});
</script>

6. 封装可重用的3D动画框架

为了更好地复用代码,我们可以将上述功能封装成一个可重用的3D动画框架。

6.1 创建Vue插件

src/plugins目录下创建一个名为threePlugin.ts的文件:

import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

export default {
  install(app) {
    app.config.globalProperties.$three = {
      createScene(container: HTMLDivElement) {
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.z = 5;
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        container.appendChild(renderer.domElement);

        const light = new THREE.PointLight(0xffffff, 1, 100);
        light.position.set(10, 10, 10);
        scene.add(light);

        const gridHelper = new THREE.GridHelper(10, 10);
        scene.add(gridHelper);

        const controls = new OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;

        const animate = () => {
          requestAnimationFrame(animate);
          controls.update();
          renderer.render(scene, camera);
        };

        animate();

        return { scene, camera, renderer, controls };
      },
      loadModel(scene: THREE.Scene, path: string) {
        const loader = new GLTFLoader();
        loader.load(path, (gltf) => {
          scene.add(gltf.scene);
        });
      },
    };
  },
};

6.2 使用Vue插件

main.ts中引入并使用该插件:

import { createApp } from 'vue';
import App from './App.vue';
import threePlugin from './plugins/threePlugin';

const app = createApp(App);
app.use(threePlugin);
app.mount('#app');

6.3 修改ThreeScene组件

ThreeScene.vue中使用封装好的插件:

<script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref } from 'vue';

export default defineComponent({
  name: 'ThreeScene',
  setup() {
    const threeContainer = ref<HTMLDivElement | null>(null);

    onMounted(() => {
      if (!threeContainer.value) return;

      const { scene, camera, renderer, controls } = (window as any).$three.createScene(threeContainer.value);
      (window as any).$three.loadModel(scene, 'path/to/your/model.glb');
    });

    return {
      threeContainer,
    };
  },
});
</script>

7. 总结

通过本文的介绍,我们学习了如何使用Vue 3、TypeScript和Three.js来构建一个高性能且易于维护的3D动画框架。从基本的3D组件创建到加载复杂的3D模型,再到添加交互功能,每一步都详细讲解了实现过程。通过封装可重用的Vue插件,我们进一步提升了代码的复用性和项目的可维护性。

希望这篇文章能为你构建3D应用提供有价值的参考,让你在Web开发的3D世界中游刃有余。