用 threejs 实现旋转的掘金logo

2,221 阅读2分钟

「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战

背景

前两天看见一个掘金大佬用threejs实现了一个自己1000粉的奖牌动画,感觉很厉害,于是乎想模仿他写个类似的demo,然后又看见掘金的logo是对称图形,很简洁大气,不要太好看(ps: 在掘金写哪能不拍拍掘金的马屁了,哈哈。。。),闲话不多说,我们开始实现吧! 动画1.gif

技术

实现

获取掘金的logo

获取掘金logo也很简单,f12检查元素,再把鼠标放到logo图片上就可以看见连接了,如下:

掘金logo:lf3-cdn-tos.bytescm.com/obj/static/…

image.png

创建一个项目

这一步我们就略过了,因为这篇vue3.0 + ts + threejs 实现简单的demo已经讲过了,如果不会的可以看上篇文章,一步一步来就可以了。

创建一个demo页面

首先创建一个demo.vue页面,然后在里面写入一下代码,并创建路由

<template>
  <div class="demo"></div>
</template>

<script lang="ts">
import ThreeJs from "./index";
import { defineComponent, onMounted } from "vue";
export default defineComponent({
  name: "Demo",
  props: {},
  setup() {
    onMounted(() => {
      new ThreeJs();
    });
  },
});
</script>
<style scoped lang="scss"></style>

引入 threejs和相关插件

  1. demo.vue文件夹内新建 index.ts 文件;
  2. 引入 threejs
  3. 引入SVG加载器和logo.svg;
  4. 引入相机控件OrbitControls
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { SVGLoader } from "three/examples/jsm/loaders/SVGLoader";

默认值和初始化

export default class ThreeJs {
  scene: THREE.Scene | null = null;
  camera: THREE.PerspectiveCamera | null = null;
  renderer: THREE.WebGLRenderer | null = null;
  ambientLight: THREE.AmbientLight | null = null;
  group: THREE.Group | null = null;
  controls: OrbitControls | null = null;
  axesHelper: THREE.AxesHelper | null = null;

  constructor() {
    this.init();
  }

  init(): void {
    this.setScene();
    this.setCamera();
    this.setRenderer();
    this.setCube();
    this.setControls();
    this.animate();
    this.onWindowResize();
    this.setAxesHelper();
  }
}

窗口变化控制

onWindowResize(): void {
    window.addEventListener("resize", () => {
      if (this.camera && this.renderer) {
        this.camera.aspect = window.innerWidth / window.innerHeight;
        this.camera.updateProjectionMatrix();
        this.render();
        this.renderer.setSize(window.innerWidth, window.innerHeight);
      }
    });
  }

辅助工具

setAxesHelper(): void {
    const helper = new THREE.AxesHelper(10);
    this.scene && this.scene.add(helper);
  }

新建一个场景

setScene(): void {
    this.scene = new THREE.Scene();
  }

新建透视相机

setCamera(): void {
    // 第二参数就是 长度和宽度比 默认采用浏览器  返回以像素为单位的窗口的内部宽度和高度
    this.camera = new THREE.PerspectiveCamera(
      45,
      window.innerWidth / window.innerHeight,
      1,
      1000
    );
    this.camera.position.set(0, 0, 400);
  }

设置渲染器

setRenderer(): void {
    this.renderer = new THREE.WebGLRenderer({ antialias: true });
    // 设置画布的大小
    this.renderer.setSize(window.innerWidth, window.innerHeight);
    //这里 其实就是canvas 画布  renderer.domElement
    document.body.appendChild(this.renderer.domElement);
  }

设置环境光

setLight(): void {
    if (this.scene) {
      // this.ambientLight = new THREE.AmbientLight(0xffffff); // 环境光
      // this.scene.add(this.ambientLight);

      this.scene.add(new THREE.AmbientLight(0x404040));

      const light = new THREE.DirectionalLight(0xffffff);
      light.position.set(1, 1, 1);
      this.scene.add(light);
    }
  }

创建logo模型

  1. 创建一个logo方法
setCube(): void {
// 代码
}
  1. 创建SVG加载器
const loader = new SVGLoader();
loader.load(url, func, func, func);
  1. 载入svg
loader.load(
      "/assets/imgs/logo.svg", func, func, func
      )
  1. 成功回调,并创建模型
(data) => {
        const paths = data.paths;
        for (let i = 0; i < paths.length; i++) {
          const path = paths[i];
          this.group = new THREE.Group();

          const material = new THREE.MeshBasicMaterial({
            color: path.color,
            side: THREE.DoubleSide,
            depthWrite: false,
          });

          const shapes = SVGLoader.createShapes(path);

          for (let j = 0; j < shapes.length; j++) {
            const shape = shapes[j];
            const geometry = new THREE.ShapeGeometry(shape);

            geometry.center();
            const mesh = new THREE.Mesh(geometry, material);
            j == 1 && mesh.translateY(6);
            j == 2 && mesh.translateY(12);
            this.group && this.group.add(mesh);
          }
        }
        this.group && (this.group.position.y = 10);
        this.group && this.group.rotateX(-3.14);
        this.scene && this.group && this.scene.add(this.group);
      },

完整代码:

setCube(): void {
    const loader = new SVGLoader();
    loader.load(
      "/assets/imgs/logo.svg",
      (data) => {
        const paths = data.paths;
        for (let i = 0; i < paths.length; i++) {
          const path = paths[i];
          this.group = new THREE.Group();

          const material = new THREE.MeshBasicMaterial({
            color: path.color,
            side: THREE.DoubleSide,
            depthWrite: false,
          });

          const shapes = SVGLoader.createShapes(path);

          for (let j = 0; j < shapes.length; j++) {
            const shape = shapes[j];
            const geometry = new THREE.ShapeGeometry(shape);

            geometry.center();
            const mesh = new THREE.Mesh(geometry, material);
            j == 1 && mesh.translateY(6);
            j == 2 && mesh.translateY(12);
            this.group && this.group.add(mesh);
          }
        }
        this.group && (this.group.position.y = 10);
        this.group && this.group.rotateX(-3.14);
        this.scene && this.group && this.scene.add(this.group);
      },
      // called when loading is in progresses
      function (xhr) {
        console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
      },
      // called when loading has errors
      function (error) {
        console.log("An error happened", error);
      }
    );
  }

渲染

 render(): void {
    if (this.renderer && this.scene && this.camera) {
      this.renderer.render(this.scene, this.camera);
    }
  }

旋转动画

 animate(): void {
    if (this.controls && this.stats) {
      //更新控制器
      this.controls.update();
      this.render();

      //更新性能插件
      this.stats.update();
      requestAnimationFrame(this.animate.bind(this));
    }
    if (this.group) {
      const axis = new THREE.Vector3(0, 1, 0); //向量axis
      this.group.rotateOnAxis(axis, Math.PI / 100); //绕axis轴旋转π/100
    }
  }

效果

动画.gif

相关文章