1. Vue3 生命周期
vs Vue2
| 生命周期 | Vue3 | Vue2 |
|---|---|---|
| dom创建前 | setup | beforeCreated |
| dom创建后 | setup | created |
| dom挂载前 | onBeforeMount | beforeMount |
| dom挂载后 | onMounted | mounted |
| dom更新前 | onBeforeUpdate | beforeUpdate |
| dom更新后 | onUpdated | updated |
| dom销毁前 | onBeforeUnmount | beforeDestory |
| dom销毁后 | onUnmounted | destoryed |
示例
<template>
<el-row :gutter="10">
<el-col :span="12">
<el-card>
<ECharts :height="'200px'" :option="option" />
</el-card>
</el-col>
</el-row>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import ECharts from '@/plugins/echarts/echarts.vue';
// 调用option值时
// const value = option.value;
const option = ref({});
onBeforeMount(()=>{ })
onMounted(()=>{ })
onBeforeUpdate(()=>{ })
onUpdated(()=>{ })
onBeforeUnmount(()=>{ })
onUnmounted(()=>{ })
</script>
<style lang="scss" scoped>
</style>
- script 中声明setup,代表dom创建前及dom创建时,并且也无需定义
defineComponent,当然这样书写也就“丢掉”了this。 - 自定义组件import后,可以直接在template中使用。
- ref函数结果在template直接使用,但在JavaScript或TypeScript中调用参数值时,需使用参数的“value”属性,类似这类函数还有reactive等,使用方法不尽相同。
2. ThreeJs
三维仿真前端框架,技术本身并不难,但涉及3D建模知识就超出程序员的能力范围,所以这个工作建议交给专业人员!新手入门建议参考这个网址的指导。
示例
<template>
<div id='virtual'></div>
</template>
<script lang='ts' setup>
import { onMounted, onBeforeUnmount } from 'vue';
import * as THREE from 'three';
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader';
import * as TWEEN from '@tweenjs/tween.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { Service } from './virtual.service';
import * as Entity from './virtual.entity';
import Header from "@/components/virtual/layout/header.vue";
// 渲染器
let webGLRenderer: any = null;
// 2D渲染图层
let css2DRenderer: any = null;
// 场景
let scene: any = null;
// 相机
let perspectiveCamera: any = null;
// 鼠标控制
let orbitControls: any = null;
// 交互点集合
let interactablePoints: Array<THREE.Sprite> = [];
// 交互点
let intersectPoint: any = null;
// 点击事件
const raycaster = new THREE.Raycaster();
// 鼠标位置
const vector2 = new THREE.Vector2();
// 获取3D模型(fbx)
const module = new Service().model();
// 交互点(接口获取)
const points = new Service().points();
onMounted(() => {
// 渲染器
webGLRenderer = new THREE.WebGLRenderer({ antialias: true })
// 像素比
webGLRenderer.setPixelRatio(window.devicePixelRatio);
// 尺寸
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
// 阴影效果
webGLRenderer.shadowMap.enabled = true;
const canvas = document.getElementById('virtual');
if (!canvas) {
throw new Error('Element is not defined');
}
canvas.appendChild(webGLRenderer.domElement);
// 2D渲染图层
css2DRenderer = new CSS2DRenderer();
css2DRenderer.setSize(window.innerWidth, window.innerHeight);
css2DRenderer.domElement.style.position = 'absolute';
css2DRenderer.domElement.style.top = '0px';
css2DRenderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(css2DRenderer.domElement);
// 场景
scene = new THREE.Scene();
// 背景色
scene.background = new THREE.Color(0x606266);
// 雾化效果
scene.fog = new THREE.Fog(0xeeeeee, 0, 100);
// 相机(透视)(视场, 长宽比, 近面, 远面)
perspectiveCamera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
perspectiveCamera.position.set(120, 100, 100);
perspectiveCamera.lookAt(new THREE.Vector3(0, 0, 0));
// 半球光源(室外效果更自然)
const boxGeometry = new THREE.BoxGeometry(0.001, 0.001, 0.001);
// 用于暗淡不光亮表面的材质
const meshLambertMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
// 网格(不渲染)
const mesh = new THREE.Mesh(boxGeometry, meshLambertMaterial);
// 平行光
const directionalLight = new THREE.DirectionalLight(0xb5b1c1, 1);
directionalLight.intensity = 1.2;
directionalLight.position.set(20, 20, 5);
directionalLight.castShadow = true;
directionalLight.target = mesh;
directionalLight.shadow.mapSize.width = 512 * 12;
directionalLight.shadow.mapSize.height = 512 * 12;
directionalLight.shadow.camera.top = 130;
directionalLight.shadow.camera.bottom = - 80;
directionalLight.shadow.camera.left = - 70;
directionalLight.shadow.camera.right = 80;
scene.add(directionalLight);
// 环境光
const ambientLight = new THREE.AmbientLight(0x605a64);
scene.add(ambientLight);
// 模型加载(.fbx(ACII))
var fbxLoader = new FBXLoader();
fbxLoader.load(
module,
(group) => {
// Object3D遍历
group.traverse(function (object3D) {
// 光照是否有阴影
object3D.castShadow = true;
// 是否接收阴影
object3D.receiveShadow = true;
});
group.rotation.y = Math.PI / 2;
group.position.set(40, 0, -50);
group.scale.set(1, 1, 1);
// 对象组合
const groupRender = new THREE.Group();
groupRender.add(group);
// 交互点
points.forEach((item) => {
let sprite = new Service().sprite();
sprite.name = item.text;
sprite.scale.set(1, 1, 1);
sprite.position.set(item.coordinate.x, item.coordinate.y, item.coordinate.z);
interactablePoints.push(sprite);
groupRender.add(sprite);
});
scene.add(groupRender);
},
(progressEvent) => {
if (Number(((progressEvent.loaded / progressEvent.total) * 100).toFixed(0)) === 100) {
const source = new Entity.Coordinate(0, 10, 20), target = new Entity.Coordinate(0, 0, 0);
new Service().animate(perspectiveCamera, orbitControls, source, target, 4000, () => { });
}
},
(errorEvent) => {
throw new Error(errorEvent.message);
}
);
// 鼠标控制
orbitControls = new OrbitControls(perspectiveCamera, webGLRenderer.domElement);
orbitControls.target.set(0, 0, 0);
orbitControls.enableDamping = true;
window.addEventListener('resize', resize, false);
webGLRenderer.domElement.style.touchAction = 'none';
webGLRenderer.domElement.addEventListener('click', mouseClick, false);
webGLRenderer.domElement.addEventListener('pointermove', mouseEnter, false);
animate();
});
/**
* VUE BeforeDestory事件
*/
onBeforeUnmount(() => {
webGLRenderer.forceContextLoss();
webGLRenderer.dispose();
scene.clear();
});
/**
* 屏幕尺寸变化
*/
const resize = () => {
perspectiveCamera.aspect = window.innerWidth / window.innerHeight;
perspectiveCamera.updateProjectionMatrix();
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
css2DRenderer.setSize(window.innerWidth, window.innerHeight);
}
/**
* 动画
*/
const animate = () => {
// 收集每帧DOM操作,在一次重绘或回流中就完成
// 重绘或回流的时间间隔跟随浏览器的刷新频率
requestAnimationFrame(animate);
webGLRenderer.render(scene, perspectiveCamera);
css2DRenderer.render(scene, perspectiveCamera);
TWEEN && TWEEN.update();
orbitControls && orbitControls.update();
}
/**
* 鼠标点击事件
*
* @param event 事件
*/
const mouseClick = (event: any) => {
// 鼠标位置
vector2.x = (event.clientX / window.innerWidth) * 2 - 1;
vector2.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 鼠标位置和当前相机矩阵计算raycaster
raycaster.setFromCamera(vector2, perspectiveCamera);
// raycaster和模型相交的数组集合
const intersects = raycaster.intersectObjects(interactablePoints);
// 清除展示点
if (intersectPoint) {
interactablePoints.map((item) => item.remove(intersectPoint));
intersectPoint = null;
}
if (intersects.length > 0) {
let intersect = intersects[0].object;
const source = new Entity.Coordinate(intersect.position.x, intersect.position.y + 4, intersect.position.z + 12),
target = new Entity.Coordinate(0, 0, 0);
new Service().animate(perspectiveCamera, orbitControls, source, target, 1000, () => {
let element = document.createElement('div');
element.className = 'popup';
element.innerHTML = intersect.name;
let css2DObject = new CSS2DObject(element);
css2DObject.position.set(0, 0, 0);
css2DObject.name = intersect.name;
intersectPoint = css2DObject;
intersect.add(css2DObject);
});
}
}
/**
* 鼠标置于画布事件
*
* @param event 事件
*/
const mouseEnter = (event: any) => {
vector2.x = (event.clientX / window.innerWidth) * 2 - 1;
vector2.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 调整视角
raycaster.setFromCamera(vector2, perspectiveCamera);
// 获取相交点
const intersects = raycaster.intersectObjects(interactablePoints, true);
if (intersects.length > 0) {
const intersect = intersects[0].object;
const sprite = intersect as THREE.Sprite;
sprite.material.color = new THREE.Color(0x337ab7);
} else {
interactablePoints.map((item) => {
item.material.color = new THREE.Color(0x409eff);
});
}
}
</script>
<style lang="scss">
.popup {
color: #ffffff;
font-size: 14px;
line-height: 30px;
padding: 20px;
border-radius: 5px;
background: rgba(96, 98, 102, .9);
}
</style>
virtual.service.ts
import * as THREE from 'three';
import * as TWEEN from '@tweenjs/tween.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import * as Entity from './virtual.entity';
export class Service {
/**
* 获取3D模型(fbx)
*
* @returns
*/
model = () => {
// TODO
return '/src/components/virtual/models/model.fbx';
};
/**
* 获取交互点
*
* @returns Array
*/
points = () => {
// TODO
return [
{
id: '0001',
text: 'Name: Mock Name A<br/>Key: Value',
coordinate: { x: -2.8, y: 4.5, z: -0.7 },
},
{
id: '0002',
text: 'Name: Mock Name B<br/>Key: Value',
coordinate: { x: -5.8, y: 2.5, z: -0.7 },
},
{
id: '0003',
text: 'Name: Mock Name C<br/>Key: Value',
coordinate: { x: 5.8, y: 2.5, z: -0.7 },
},
];
};
/**
* 交互点,点击弹层
*
* @returns THREE.Sprite
*/
sprite = (
width: number = 100,
height: number = 100,
radius: number = 10,
lineWidth: number = 5
) => {
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
if (!context) {
throw new Error('Element is not defined');
}
context.beginPath();
const x = (width + lineWidth) / 2,
y = (height + lineWidth) / 2;
context.arc(x, y, radius, 0, Math.PI * 2, true);
context.closePath();
// 背景色
context.fillStyle = 'dark';
context.fill();
// 边框
context.strokeStyle = 'white';
context.lineWidth = lineWidth;
context.lineCap = 'round';
context.stroke();
const texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
const spriteMaterial = new THREE.SpriteMaterial({
map: texture,
transparent: true,
opacity: 0.8,
});
return new THREE.Sprite(spriteMaterial);
};
/**
* 动画
*
* @param perspectiveCamera 相机
* @param orbitControls 鼠标控制
* @param source 坐标点
* @param target 坐标点
* @param time 时间
* @param callBack 回调
*/
animate = (
perspectiveCamera: THREE.PerspectiveCamera,
orbitControls: OrbitControls,
source: Entity.Coordinate,
target: Entity.Coordinate,
time: number,
callBack: () => void
) => {
const tween = new TWEEN.Tween({
// 相机(x)
x1: perspectiveCamera.position.x,
// 相机(y)
y1: perspectiveCamera.position.y,
// 相机(z)
z1: perspectiveCamera.position.z,
// 控制点中心点(x)
x2: orbitControls.target.x,
// 控制点中心点(y)
y2: orbitControls.target.y,
// 控制点中心点(z)
z2: orbitControls.target.z,
});
tween.to(
{
x1: source.x,
y1: source.y,
z1: source.z,
x2: target.x,
y2: target.y,
z2: target.z,
},
time
);
tween.onUpdate(function (object) {
perspectiveCamera.position.x = object.x1;
perspectiveCamera.position.y = object.y1;
perspectiveCamera.position.z = object.z1;
orbitControls.target.x = object.x2;
orbitControls.target.y = object.y2;
orbitControls.target.z = object.z2;
orbitControls.update();
});
tween.onComplete(function () {
orbitControls.enabled = true;
callBack();
});
tween.easing(TWEEN.Easing.Cubic.InOut);
tween.start();
};
}
virtual.entity.ts
/**
* 三维坐标
*/
export class Coordinate {
// X轴
x!: number;
// Y轴
y!: number;
// Z轴
z!: number;
constructor(x: number, y: number, z: number) {
this.x = x;
this.y = y;
this.z = z;
}
}
3. 总结
Vue3 / TypeScript / Vite / Vue-Router / Vuex / Axios / Element-Plus / Echarts / ThreeJs 这些技术集成并没有多难,使用官网文档就足够,Vue3相较于Vue2的一些新特性,使用起来需要适应一下,注意:Vue3是向下兼容的,老系统升级也没必要过度求新,新手找个业务功能写写,熟能生巧。