「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」
背景
前两天看见一个掘金大佬用threejs实现了一个自己1000粉的奖牌动画,感觉很厉害,于是乎想模仿他写个类似的demo,然后又看见掘金的logo是对称图形,很简洁大气,不要太好看(ps: 在掘金写哪能不拍拍掘金的马屁了,哈哈。。。),闲话不多说,我们开始实现吧!
技术
vue3.0typescript- threejs
实现
获取掘金的logo
获取掘金logo也很简单,f12检查元素,再把鼠标放到logo图片上就可以看见连接了,如下:
创建一个项目
这一步我们就略过了,因为这篇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和相关插件
- 在
demo.vue文件夹内新建index.ts文件; - 引入
threejs; - 引入SVG加载器和logo.svg;
- 引入相机控件
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模型
- 创建一个logo方法
setCube(): void {
// 代码
}
- 创建SVG加载器
const loader = new SVGLoader();
loader.load(url, func, func, func);
- 载入svg
loader.load(
"/assets/imgs/logo.svg", func, func, func
)
- 成功回调,并创建模型
(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
}
}