vue+threejs写界面:标注闪烁动画

490 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第19天,点击查看活动详情


写在前面

本文用vue+threejs实现标注闪烁动画。

代码说明

实现标注发光动画的关键其实是写css动画。

效果gif如下:

20221014_112409.gif

标注跳跃动画的效果就是模型上面的标注会进行闪烁。关于threejs相机、场景、渲染器等的创建可以自行查看代码中的注释。

首先,我们在mounted()方法中调用initLabelRenderer()创建 CSS 2D渲染器。

然后,使用CSS2DRenderer和CSS2DObject给模型添加标注,添加标注的代码在initModel()方法中,首先获取标注的html节点,将该节点添加到CSS2DObject中,并且设置该标注的位置position,然后在下面调用render()方法的this.labelRenderer.render(this.scene, this.camera);的时候会一起被渲染出来。

          this.Rabbit = document.getElementById("Rabbit");
          const RabbitTag = new CSS2DObject(this.Rabbit);
          RabbitTag.position.set(
            gltf.scene.position.x,
            0.15,
            gltf.scene.position.z
          );
          gltf.scene.add(RabbitTag);
          RabbitTag.layers.set(0);

接下来,看看是怎么实现的标注闪烁,关键代码:

给标注所在的元素name添加动画animation: aniName linear 1.5s infinite;动画的名称是aniName,动画的速度曲线是linear,动画持续的时间是1.5s,动画播放的次数是无数次infinite。

scale是改变元素的缩放程度,随着动画的执行,元素的缩放程度从1变成1.5再变回1.

  .name {
    ...
    animation: aniName linear 1.5s infinite;
  }
  @keyframes aniName {
    0%,
    100% {
      transform: scale(1);
    }

    50% {
      transform: scale(1.5);
    }
  }

至此,我们就实现了给模型添加标注闪烁动画。

完整代码

<template>
  <div class="item">
    <div id="THREE54">
      <div id="Rabbit">
        <div class="name">森林小屋</div>
      </div>
    </div>
  </div>
</template>

<script>
import * as THREE from "three";

import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";

import {
  CSS2DRenderer,
  CSS2DObject,
} from "three/examples/jsm/renderers/CSS2DRenderer.js"; // CSS 2D渲染器

export default {
  data() {
    return {
      camera: null,
      scene: null,
      gltfLoader: null,
      renderer: null,
      controls: null,

      labelRenderer: null,
    };
  },
  mounted() {
    this.gltfLoader = new GLTFLoader();
    this.initScene(); // 创建场景
    this.initCamera(); // 创建相机
    this.initLight(); // 创建灯光
    this.initRenderer(); // 创建渲染器
    this.initLabelRenderer(); // 创建 CSS 2D渲染器
    this.initControls(); //创建轨道控制器
    this.initModel(); // 加载模型
  },
  methods: {
    initScene() {
      this.scene = new THREE.Scene();
      this.scene.background = new THREE.Color(0x000000); // 设置场景背景颜色
    },
    initCamera() {
      this.camera = new THREE.PerspectiveCamera(
        35,
        (window.innerWidth - 201) / window.innerHeight,
        1,
        500
      ); // 透视相机
      this.camera.position.x = 0.5;
      this.camera.position.y = 0.5; // 设置相机的位置
      this.camera.position.z = 1.8;
      this.scene.add(this.camera); // 将相机添加到场景中
    },
    initLight() {
      const light = new THREE.DirectionalLight(0xffffff); // 平行光
      light.position.set(0.5, 1.0, 0.5).normalize(); // 设置平行光的方向,从(0.5, 1.0, 0.5)->target一般(0, 0, 0)
      this.scene.add(light); // 将灯光添加到场景中

      const ambLight = new THREE.AmbientLight(0xf0f0f0, 0.1); // 环境光
      this.scene.add(ambLight);
    },
    initRenderer() {
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.renderer.outputEncoding = THREE.sRGBEncoding;
      this.renderer.setPixelRatio(window.devicePixelRatio);
      this.renderer.setSize(window.innerWidth - 201, window.innerHeight);
      document.getElementById("THREE54").appendChild(this.renderer.domElement);
    },
    initLabelRenderer() {
      this.labelRenderer = new CSS2DRenderer();
      this.labelRenderer.setSize(window.innerWidth - 201, window.innerHeight);
      this.labelRenderer.domElement.style.position = "absolute";
      this.labelRenderer.domElement.style.top = "0px";
      document
        .getElementById("THREE54")
        .appendChild(this.labelRenderer.domElement);
    },
    initControls() {
      this.controls = new OrbitControls(
        this.camera,
        this.labelRenderer.domElement
      );
      this.controls.addEventListener("change", this.render);
      this.controls.enableDamping = true; // 开启惯性
    },
    initModel() {
      this.gltfLoader.load(
        "/models/models/gltf/forest_house/scene.gltf",
        (gltf) => {
          gltf.scene.scale.set(3, 3, 3);

          this.Rabbit = document.getElementById("Rabbit");
          const RabbitTag = new CSS2DObject(this.Rabbit);
          RabbitTag.position.set(
            gltf.scene.position.x,
            0.15,
            gltf.scene.position.z
          );
          gltf.scene.add(RabbitTag);
          RabbitTag.layers.set(0);

          this.scene.add(gltf.scene);

          this.animate();
        }
      );
    },
    toggleIsTwinkle() {
      this.isTwinkle = !this.isTwinkle;
    },
    animate() {
      requestAnimationFrame(this.animate);

      this.controls.update();

      this.render();
    },
    render() {
      this.renderer.render(this.scene, this.camera);
      this.labelRenderer.render(this.scene, this.camera);
    },
  },
};
</script>

<style lang="less" scoped>
#Rabbit {
  position: relative;
  .name {
    position: absolute;
    width: 80px;
    height: 20px;
    line-height: 20px;
    left: -40px;
    top: 6px;
    left: -36px;
    text-align: center;
    font-size: 16px;
    font-weight: bold;
    color: rgb(63, 26, 224);
    background-color: #fff;
    animation: aniName linear 1.5s infinite;
  }
  @keyframes aniName {
    0%,
    100% {
      transform: scale(1);
    }

    50% {
      transform: scale(1.5);
    }
  }
}
</style>

写在最后

以上就是所有的代码和说明。