企业项目管理、ORK、研发管理与敏捷开发工具平台

网站首页 > 精选文章 正文

在uniapp中实现3D模型展示:基于Three.js的组件开发实践

wudianyun 2025-06-12 16:48:28 精选文章 9 ℃

# 在Uni-App中实现3D模型展示:基于Three.js的组件开发实践

## 引言

在现代移动应用开发中,3D模型展示已成为提升用户体验的重要手段。本文将基于我开发的`threeCom`组件,详细介绍如何在Uni-App框架中集成Three.js来实现3D模型的加载、展示和交互功能。这个组件支持模型加载进度显示、自动旋转、轨道控制以及全屏展示等功能,是一个完整的3D模型展示解决方案。

## 安装与引入方式:

通过npm安装:npm install three

直接下载源码:从GitHub获取three.js-master文件,放入项目的static文件夹

## 组件架构设计

### 组件结构

`threeCom`组件采用Vue 3的Composition API编写,主要分为三个部分:

1. **模板部分**:负责UI布局和交互元素

2. **脚本部分**:处理业务逻辑和状态管理

3. **RenderJS部分**:实际运行Three.js的代码

<template>

<view :id="'threejsOutStaticDiv' + time" class="threejsOutDiv">

<view :id="'threejsOutDiv' + time" class="threejsOutDiv">

<!-- Three.js渲染容器 -->

<view :id="'threejsContent' + time" :prop="threejsProp" :change:prop="Threejs.create"></view>

<!-- 加载进度条 -->

<view class="progressItemDiv" v-if="percent != 2">

<view class="progressItemOutDiv">

<view class="progressDiv" :style="`width: calc(100% * ${percent})`"></view>

<view class="progressBorder"></view>

</view>

</view>

<!-- 全屏控制按钮 -->

<text id="enterScreen" v-show="!isScreen && percent == 2 && needScreen"

class="iconfont screenBtnFont"></text>

<view v-show="isScreen && percent == 2 && needScreen" id="exitScreen" class="iconfont screenBtnFont safeBottom">

<text></text>

<!-- 适配iOS底部安全区域 -->

<gui-iphone-bottom :customClass="['iphoneBottom']"></gui-iphone-bottom>

</view>

</view>

</view>

</template>

## 核心功能实现

### 1. 模型加载与进度显示

组件使用GLTFLoader来加载glTF格式的3D模型,并实现了加载进度回调:

loader.load(gltfSrc, (gltf) => {

// 模型加载完成回调

this.$ownerInstance.callMethod('modelLoadingProgress', '2');

// 模型自适应大小

const box = new THREE.Box3().setFromObject(gltfScene);

const centerData = box.getCenter(new THREE.Vector3());

const maxDim = Math.max(

box.max.x - box.min.x,

box.max.y - box.min.y,

box.max.z - box.min.z

);

const scale = 600 / maxDim;

gltfScene.scale.set(scale, scale, scale);

gltfScene.position.set(

(0 - centerData.x) * scale,

(0 - centerData.y) * scale,

(0 - centerData.z) * scale

);

scene.add(gltfScene);

}, (xhr) => {

// 加载进度回调

const percent = xhr.loaded / xhr.total;

this.$ownerInstance.callMethod('modelLoadingProgress', percent + '');

});

```

### 2. 渲染循环与自动旋转

组件实现了渲染循环和自动旋转功能,当用户没有交互时会自动旋转模型:

```javascript

const render = () => {

let t1 = new Date();

let t = t1 - t0;

t0 = t1;

animationFrameId = requestAnimationFrame(render);

renderer.render(scene, camera);

// 自动旋转逻辑

if (this.obj4.isRotate && needRotate) {

scene.rotateY(0.0005 * t);

}

}

render();

```

### 3. 轨道控制交互

组件提供了精细的轨道控制配置,包括缩放限制和平移控制:

```javascript

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

controls.maxZoom = 2.5;

controls.minZoom = 1;

controls.zoomSpeed = 0.5;

controls.enablePan = false;

// 交互状态监听

controls.addEventListener('change', () => {

this.obj4.isRotate = false;

});

controls.addEventListener('end', (e) => {

autoRotateSetTimeOut = setTimeout(() => {

this.obj4.isRotate = true;

}, 1500);

});

```

### 4. 全屏展示功能

组件实现了完整的全屏/退出全屏逻辑,包括尺寸调整和过渡动画:

```javascript

async screen(isScreen) {

if (isScreen) {

// 进入全屏逻辑

const threejsOutDiv = document.getElementById(this.compData.threejsOutDivId);

const divTemp = document.createElement('div');

divTemp.classList.add('threejsMengCeng');

// 设置初始尺寸和位置

divTemp.style.height = threejsOutDiv.getBoundingClientRect().height + 'px';

divTemp.style.width = threejsOutDiv.getBoundingClientRect().width + 'px';

divTemp.style.position = 'absolute';

divTemp.style.zIndex = '910';

divTemp.style.top = threejsOutDiv.getBoundingClientRect().top + 'px';

divTemp.style.left = threejsOutDiv.getBoundingClientRect().left + 'px';

divTemp.style.transition = 'all 200ms ease';

document.body.appendChild(divTemp);

divTemp.appendChild(threejsOutDiv);

// 动画过渡到全屏

setTimeout(() => {

divTemp.style.height = document.documentElement.clientHeight + 'px';

divTemp.style.width = document.documentElement.clientWidth + 'px';

divTemp.style.top = '0';

divTemp.style.left = '0';

// 调整渲染器和相机

if (this.rendererComp && this.cameraComp) {

const widthTemp = document.documentElement.clientWidth;

const heightTemp = document.documentElement.clientHeight;

this.rendererComp.setSize(widthTemp, heightTemp);

var k = widthTemp / heightTemp;

var s = 500;

this.cameraComp.left = -s * k;

this.cameraComp.right = s * k;

this.cameraComp.top = s;

this.cameraComp.bottom = -s;

this.cameraComp.updateProjectionMatrix();

}

this.vm.callMethod('setIsScreen', true);

}, 100);

} else {

// 退出全屏逻辑

// ...类似的处理逻辑

}

}

```

## 性能优化实践

### 1. 资源释放管理

组件在卸载时确保释放所有Three.js资源,避免内存泄漏:

```javascript

unmounted() {

if (animationFrameId) cancelAnimationFrame(animationFrameId);

if (autoRotateSetTimeOut) clearTimeout(autoRotateSetTimeOut);

// 释放各种Three.js对象

if (ambient) ambient.dispose();

if (renderer) renderer.dispose();

if (controls) controls.dispose();

// 清空引用

camera = null;

scene = null;

gltfScene = null;

loader = null;

}`

### 2. 跨平台兼容处理

组件针对不同平台做了兼容处理,特别是iOS的安全区域:

<!-- 适配iOS底部安全区域 -->

<!-- #ifdef APP-PLUS -->

<gui-iphone-bottom :customClass="['iphoneBottom']"></gui-iphone-bottom>

<!-- #endif -->

### 3. 渲染性能优化

使用正投影相机(OrthographicCamera)而非透视相机,减少计算量:

camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 2000);`

设置渲染器抗锯齿和透明背景:

renderer = new THREE.WebGLRenderer({

antialias: true,

});

renderer.setClearColor(0x000000, 0); // 透明背景

## 使用示例

在父组件中使用`threeCom`非常简单:

<template>

<view class="container">

<threeCom :src="modelUrl"

:needScreen="true"

:needRotate="true"

:needControl="true"

@modelLoadingProgress="onModelLoadingProgress" />

</view>

</template>

<script lang="ts" setup>

import { ref } from 'vue'

import threeCom from '@/components/threeCom.vue'

const modelUrl = ref('https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/Duck/glTF/Duck.gltf')

const progress = ref(0)

function onModelLoadingProgress(e: { detail: { progress: number } }) {

progress.value = Math.floor(e.detail.progress * 100)

}

</script>

```

threeCom.vue

```javascript

<template>

<view :id="'threejsOutStaticDiv' + time" class="threejsOutDiv">

<view :id="'threejsOutDiv' + time" class="threejsOutDiv">

<view :id="'threejsContent' + time" :prop="threejsProp" :change:prop="Threejs.create"></view>

<view class="progressItemDiv" v-if="percent != 2">

<!-- <view class="progressItemDiv"> -->

<view class="progressItemOutDiv">

<view class="progressDiv" :style="`width: calc(100% * ${percent})`"></view>

<view class="progressBorder"></view>

</view>

</view>

<text id="enterScreen" v-show="!isScreen && percent == 2 && needScreen"

class="iconfont screenBtnFont"></text>

<view v-show="isScreen && percent == 2 && needScreen" id="exitScreen" class="iconfont screenBtnFont safeBottom">

<text></text>

<!-- #ifdef APP-PLUS -->

<gui-iphone-bottom :customClass="['iphoneBottom']"></gui-iphone-bottom>

<!-- #endif -->

</view>

</view>

</view>

</template>

<script lang="ts">

import { reactive, ref, toRefs, computed } from "vue";

export default {

props: {

src: {

default: ''

},

needScreen: {

default: false,

},

needRotate: {

default: false,

},

needControl: {

default: true,

},

},

setup(props: any) {

const threejsProp = computed(() => {

return {

src: props.src,

width: data.width,

height: data.height,

threejsContentId: 'threejsContent' + data.time,

threejsOutDivId: 'threejsOutDiv' + data.time,

threejsOutStaticDivId: 'threejsOutStaticDiv' + data.time,

needScreen: props.needScreen,

needRotate: props.needRotate,

needControl: props.needControl,

}

})

const data = reactive({

time: new Date().getTime(),

width: '',

height: '',

percent: 0,

isScreen: false,

});

const modelLoadingProgress = (progress) => {

// console.log('加载进度' + progress);

// if (progress == 2) { // 表示加载完成并开始渲染

// } else {

// }

data.percent = progress

}

const setIsScreen = (val) => {

data.isScreen = val;

}

const pageData = toRefs(data);

return {

...pageData,

threejsProp,

modelLoadingProgress,

setIsScreen,

};

},

mounted() {

this.$nextTick(() => {

const query = uni.createSelectorQuery().in(this);

query.select(`#threejsOutDiv${this.time}`).boundingClientRect((res) => {

this.width = res.width

this.height = res.height

}).exec();

});

}

};

</script>

<script module="Threejs" lang="renderjs">

import * as THREE from 'three'

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

import Stats from 'three/examples/jsm/libs/stats.module.js';

import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';

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

let scene = ''

let gui = ''

let autoRotateSetTimeOut = '' // 自开开始旋转计时器

let ambient = '' // 灯光

let camera = '' // 相机

let renderer = ''

let controls = ''

let animationFrameId = ''

let gltfScene = ''

let loader = ''

export default {

data(){

return {

compData: {},

vm: '',

rendererComp: '',

cameraComp: '',

kComp: '',

obj4: {

isRotate: true,

},

}

},

unmounted() {

if (animationFrameId) {

cancelAnimationFrame(animationFrameId)

}

if (autoRotateSetTimeOut) {

clearTimeout(autoRotateSetTimeOut)

autoRotateSetTimeOut = null

}

if (ambient) {

ambient.dispose()

ambient = null

}

// if (camera) {

// camera.dispose() // 报错

// }

camera = null

if (renderer) {

renderer.dispose()

renderer = null

}

if (controls) {

controls.dispose()

controls = null

}

// if (scene) {

// scene.dispose() // 报错

// }

scene = null

// if (gltfScene) {

// gltfScene.dispose() // 报错

// }

gltfScene = null

// if (loader) {

// loader.dispose() // 报错

// }

loader = null

},

methods: {

async create(newVal, oldVal, vm) {

this.vm = vm;

let newValue = newVal;

let newObj = '';

if (newValue && typeof(newValue) === 'string') {

if (newValue.indexOf('json://') === 0) {

newValue = newValue.replace('json://', '');

}

// console.log('------', newValue);

try{

newObj = JSON.parse(newValue);

}catch(e){

//TODO handle the exception

console.error('JSONParse Err----catch!!');

}

} else if (newValue) {

newObj = newValue;

}

if (!newObj) {

console.error('renderJsNewObj is null!!');

return;

}

let {

src,

width,

height,

threejsContentId,

threejsOutDivId,

threejsOutStaticDivId,

needRotate,

needControl,

} = newObj;

this.compData = newObj

if (src && width && height) {

if (!gui) {

// gui = new GUI();

// gui.domElement.style.right = '0px';

// gui.domElement.style.top = '50px';

// gui.domElement.style.width = '200px';

}

/**

* 创建场景对象Scene

*/

scene = new THREE.Scene();

// 辅助观察坐标系 坐标轴颜色红R、绿G、蓝B分别对应坐标系的x、y、z轴,对于three.js的3D坐标系默认y轴朝上

// const axesHelper = new THREE.AxesHelper(150);

// scene.add(axesHelper);

/**

* 创建网格模型

*/

//创建一个球体几何对象

// var geometry = new THREE.SphereGeometry(60, 40, 40);

//创建一个立方体几何对象Geometry 长、高、宽

// var geometry = new THREE.BoxGeometry(100, 100, 200);

// const textLoader = new THREE.TextureLoader();

// let tietuSrc = '/static/images/tietu.png'

// const texture = textLoader.load(await this.getfile(tietuSrc))

//材质对象Material

// var material = new THREE.MeshLambertMaterial({

// // color: 0x0000ff, // 贴图时不需要设置颜色

// map: texture,

// });

// 高光材质 模拟镜面反射,产生一个高光效果

// var material = new THREE.MeshPhongMaterial({

// color: 0x0000ff, // 贴图时不需要设置颜色

// shininess: 20, //高光部分的亮度,默认30

// // specular: 0xffd700, //高光部分的颜色

// });

// 材质半透明,不受光照影响

// var material = new THREE.MeshBasicMaterial({

// color: 0x0000ff,

// transparent:true,//开启透明

// opacity:0.5,//设置透明度

// });

//网格模型对象Mesh

// var mesh = new THREE.Mesh(geometry, material);

// 设置模型mesh的xyz坐标

// mesh.position.set(50, 50, 100)

// gui.add(mesh.position, 'x', 0, 180).name('x轴').step(0.06);

// gui.add(mesh.position, 'y', 0, 180).name('y轴').step(0.05);

// gui.add(mesh.position, 'z', 0, 180).name('z轴').step(0.05);

// const obj = {

// color:0x0000ff,

// };

// .addColor()生成颜色值改变的交互界面

// gui.addColor(obj, 'color').name('颜色').onChange((value) => {

// mesh.material.color.set(value);

// });

// 下拉菜单

// const obj2 = {

// scale: 0,

// };

// 参数3数据类型:数组(下拉菜单)

// gui.add(obj2, 'scale', [-100, 0, 100]).name('y坐标').onChange(function (value) {

// mesh.position.y = value;

// });

// const obj3 = {

// scale: 0,

// };

// 参数3数据类型:对象(下拉菜单)

// gui.add(obj3, 'scale', {

// left: -100,

// center: 0,

// right: 100

// // 左: -100,//可以用中文

// // 中: 0,

// // 右: 100

// }).name('位置选择').onChange(function (value) {

// mesh.position.x = value;

// });

// const obj5 = {

// scale: 1,

// }

// gui.add(obj5, 'scale', 0.1, 5).name('缩放模型').step(0.01).onChange((value) => {

// mesh.scale.set(value, value, value)

// });

//网格模型添加到场景中

// scene.add(mesh);

// 创建GLTF加载器对象

loader = new GLTFLoader();

loader.setCrossOrigin('anonymous')

// let gltfSrc = '/static/gltf/untitled.glb'

// let gltfSrc = '/static/gltf/nftboli.gltf'

let gltfSrc = src

// loader.load(await this.getfile(gltfSrc), ( gltf ) => {

loader.load(gltfSrc, ( gltf ) => {

// console.log('loading finish')

this.$ownerInstance.callMethod('modelLoadingProgress', '2');

const enterScreenBtn = document.getElementById('enterScreen');

if (enterScreenBtn) {

enterScreenBtn.addEventListener('click', () => {

this.screen(true)

})

}

const exitScreenBtn = document.getElementById('exitScreen');

if (exitScreenBtn) {

exitScreenBtn.addEventListener('click', () => {

this.screen(false)

})

}

// console.log('控制台查看加载gltf文件返回的对象结构',gltf);

// console.log('gltf对象场景属性',gltf.scene);

gltfScene = gltf.scene

// 模型自适应大小

const box = new THREE.Box3().setFromObject(gltfScene)

// console.log('box-----', box.getCenter(new THREE.Vector3()))

const x = (box.max.x - box.min.x)

const y = (box.max.y - box.min.y)

const z = (box.max.z - box.min.z)

const maxDim = Math.max(x, y, z)

const scale = 600 / maxDim

gltfScene.scale.set(scale,scale, scale)

// 自动设置到中心点

const centerData = box.getCenter(new THREE.Vector3())

// gltfScene.translateX((0 - centerData.x) * scale)

// gltfScene.translateY((0 - centerData.y) * scale)

// gltfScene.translateZ((0 - centerData.z) * scale)

gltfScene.position.set((0 - centerData.x) * scale, (0 - centerData.y) * scale, (0 - centerData.z) * scale)

// console.log('gltfScene--', gltfScene.position)

// gltfScene.traverse( function ( child ) {

// if ( child.isMesh ) {

// child.geometry.center(); // center here

// }

// });

scene.add(gltfScene);

}, (xhr) => {

const percent = xhr.loaded / xhr.total;

// console.log('percent--', percent)

this.$ownerInstance.callMethod('modelLoadingProgress', percent + '');

})

/**

* 光源设置

*/

//点光源, 参数为光色、光照强度

// var point = new THREE.PointLight(0xffffff, 2);

// //点光源位置

// point.position.set(400, 190, 0);

// gui.add(point, 'intensity', 0, 2.0).name('光强')

// //点光源添加到场景中

// scene.add(point);

// const pointLightHelper = new THREE.PointLightHelper(point, 10);

// scene.add(pointLightHelper);

//环境光

ambient = new THREE.AmbientLight(0xffffff);

scene.add(ambient);

/**

* 相机设置

*/

// var width = window.innerWidth; //窗口宽度

// var height = window.innerHeight; //窗口高度

// var width = window.innerWidth; //窗口宽度

// var height = 400; //窗口高度

var k = width / height; //窗口宽高比

var s = 500; //三维场景显示范围控制系数,系数越大,显示的范围越大

//创建相机对象

camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 2000); // 正投影相机

// const camera = new THREE.PerspectiveCamera(100, width / height, 1, 2000); // 透视相机。模拟人眼观察

camera.focus = 100

camera.position.set(0, 0, 500); //设置相机位置

// camera.lookAt(mesh.position); //设置相机方向(指向的场景对象)

// gltfScene && camera.lookAt(gltfScene.position); //设置相机方向(指向的场景对象)

camera.lookAt(0, 0, 0); // //指向坐标原点

camera.updateProjectionMatrix();

// 画布跟随窗口变化

window.onresize = () => {

// const widthTemp = window.innerWidth; //canvas画布高度

// const heightTemp = window.innerWidth / k; //canvas画布宽度

// renderer.setSize(widthTemp, heightTemp);

// camera.aspect = widthTemp / heightTemp;

// camera.updateProjectionMatrix();

};

// const obj5 = {

// scale: 1,

// }

// gui.add(obj5, 'scale', 0, 5).name('缩放相机').step(0.01).onChange((value) => {

// camera.zoom = value

// camera.updateProjectionMatrix()

// });

/**

* 创建渲染器对象

*/

renderer = new THREE.WebGLRenderer({

antialias: true, // 锯齿模糊

});

// 获取你屏幕对应的设备像素比.devicePixelRatio告诉threejs,以免渲染模糊问题

renderer.setPixelRatio(window.devicePixelRatio);

renderer.setSize(width, height);//设置渲染区域尺寸

// renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色

renderer.setClearColor(0x000000, 0); //设置背景颜色

renderer.outputEncoding = THREE.sRGBEncoding;

const threejsContentDom = document.getElementById(threejsContentId)

// const stats = new Stats();

if (threejsContentDom) {

threejsContentDom.appendChild(renderer.domElement); //threejsContent元素中插入canvas对象

// threejsContentDom.appendChild(stats.domElement)

}

//执行渲染操作 指定场景、相机作为参数(---渲染---)

// renderer.render(scene, camera);

// const obj4 = {

// isRotate: true,

// };

// 如果.add()改变属性的对应的数据类型如果是布尔值,那么交互界面就是一个单选框。

// gui.add(obj4, 'isRotate').name('是否旋转')

// 自动循环旋转模型 (---渲染---)

let t0 = new Date(); // 上次时间

const render = () => {

let t1 = new Date() // 本地时间

let t = t1 - t0; // 时间差

t0 = t1 //把本次时间赋值给上次时间

// stats.update();

animationFrameId = requestAnimationFrame(render) // 相当于settimeout 正常是16.7毫秒执行一次,相当于1秒60帧

renderer.render(scene, camera); //执行渲染操作

if (this.obj4.isRotate) {

// mesh.rotateY(0.0005 * t) // //每次绕y轴旋转0.01弧度 注意是围绕Y

// mesh.rotateY(0.0005 * t) // //每次绕y轴旋转0.01弧度 注意是围绕Y

// gltfScene && gltfScene.rotateY(0.0008 * t)

if (needRotate) {

scene.rotateY(0.0005 * t)

}

// scene.rotateY(0.0005 * t)

}

}

render()

// console.log('scene---', scene)

// 鼠标操作模型 (---渲染---)

// const render = () => {

// renderer.render(scene, camera); //执行渲染操作

// }

// render()

if (needControl) {

controls = new OrbitControls(camera, renderer.domElement);//创建控件对象

controls.maxZoom = 2.5

controls.minZoom = 1

controls.zoomSpeed = 0.5

controls.enablePan = false

// controls.maxDistance = 500

// controls.minDistance = 220

if (controls) {

controls.addEventListener('change', () => {

if (autoRotateSetTimeOut) {

clearTimeout(autoRotateSetTimeOut)

}

this.obj4.isRotate = false

});//监听鼠标、键盘事件

controls.addEventListener('start', (e) => {

if (autoRotateSetTimeOut) {

clearTimeout(autoRotateSetTimeOut)

}

// console.log('启动交互---', e)

this.obj4.isRotate = false

})

controls.addEventListener('end', (e) => {

if (autoRotateSetTimeOut) {

clearTimeout(autoRotateSetTimeOut)

}

// console.log('停止交互---')

autoRotateSetTimeOut = setTimeout(() => {

this.obj4.isRotate = true

}, 1500)

})

}

}

this.rendererComp = renderer

this.cameraComp = camera

}

},

getfile(src) {

// #ifdef APP-PLUS

let url = plus.io.convertLocalFileSystemURL(src)

return new Promise((resolve,reject)=>{

plus.io.resolveLocalFileSystemURL(url, entry => {

var reader = null;

entry.file( file => {

reader = new plus.io.FileReader();

reader.onloadend = ( read )=> {

resolve(read.target.result)

};

reader.readAsDataURL( file );

}, function ( error ) {

alert( error.message );

} );

}, err => {

resolve(src)

})

})

// #endif

// #ifndef APP-PLUS

return new Promise((resolve,reject)=>{

resolve(src)

})

// #endif

},

async screen(isScreen) {

if (isScreen) { // 全屏

await this.removeThreejsMengCeng();

const threejsOutDiv = document.getElementById(this.compData.threejsOutDivId);

const divTemp = document.createElement('div');

if (divTemp) {

divTemp.classList.add('threejsMengCeng');

divTemp.style.height = threejsOutDiv.getBoundingClientRect().height + 'px';

divTemp.style.width = threejsOutDiv.getBoundingClientRect().width + 'px';

divTemp.style.position = 'absolute';

divTemp.style.zIndex = '910';

divTemp.style.top = threejsOutDiv.getBoundingClientRect().top + 'px';

divTemp.style.left = threejsOutDiv.getBoundingClientRect().left + 'px';

divTemp.style.transition = 'all 200ms ease';

const pageDiv = document.getElementsByTagName('body')[0];

if (pageDiv) {

pageDiv.appendChild(divTemp);

}

}

if (threejsOutDiv) {

setTimeout(() => {

divTemp.appendChild(threejsOutDiv);

divTemp.style.height = document.documentElement.clientHeight + 'px';

divTemp.style.width = document.documentElement.clientWidth + 'px';

divTemp.style.top = '0';

divTemp.style.left = '0';

setTimeout(() => {

if (this.rendererComp && this.cameraComp) {

const widthTemp = document.documentElement.clientWidth; //canvas画布高度

const heightTemp = document.documentElement.clientHeight; //canvas画布宽度

this.rendererComp.setSize(widthTemp, heightTemp);

var k = widthTemp / heightTemp; //窗口宽高比

var s = 500; //三维场景显示范围控制系数,系数越大,显示的范围越大

// OrthographicCamera(-s * k, s * k, s, -s, 1, 2000); // 正投影相机

this.cameraComp.left = -s * k

this.cameraComp.right = s * k

this.cameraComp.top = s

this.cameraComp.bottom = -s

this.cameraComp.updateProjectionMatrix();

}

}, 0)

this.vm.callMethod('setIsScreen', true);

}, 100)

}

} else { // 退出全屏

const threejsOutStaticDivId = document.getElementById(this.compData.threejsOutStaticDivId);

const threejsOutDivId = document.getElementById(this.compData.threejsOutDivId);

const child = document.getElementsByClassName('threejsMengCeng')[0];

if (child) {

child.style.height = threejsOutStaticDivId.getBoundingClientRect().height + 'px';

child.style.width = threejsOutStaticDivId.getBoundingClientRect().width + 'px';

child.style.top = threejsOutStaticDivId.getBoundingClientRect().top + 'px';

child.style.left = threejsOutStaticDivId.getBoundingClientRect().left + 'px';

}

if (this.rendererComp && this.cameraComp) {

const widthTemp = threejsOutStaticDivId.getBoundingClientRect().width; //canvas画布高度

const heightTemp = threejsOutStaticDivId.getBoundingClientRect().height; //canvas画布宽度

this.rendererComp.setSize(widthTemp, heightTemp);

var k = widthTemp / heightTemp; //窗口宽高比

var s = 500; //三维场景显示范围控制系数,系数越大,显示的范围越大

// OrthographicCamera(-s * k, s * k, s, -s, 1, 2000); // 正投影相机

this.cameraComp.left = -s * k

this.cameraComp.right = s * k

this.cameraComp.top = s

this.cameraComp.bottom = -s

this.cameraComp.updateProjectionMatrix();

}

this.vm.callMethod('setIsScreen', false);

setTimeout(async () => {

if (threejsOutStaticDivId) {

threejsOutStaticDivId.appendChild(threejsOutDivId);

}

setTimeout(() => {

this.removeThreejsMengCeng();

}, 500)

}, 600);

}

},

removeThreejsMengCeng() {

return new Promise((resolve) => {

const bodyEle = document.getElementsByTagName('body')[0];

if (bodyEle) {

const child = document.getElementsByClassName('threejsMengCeng');

if (child) {

if (child.length) {

for (let i = 0; i < child.length; i++) {

bodyEle.removeChild(child[i]);

}

}

}

}

resolve();

});

}

},

}

</script>

<style scoped lang="scss">

.threejsOutDiv {

width: 100%;

height: 100%;

display: flex;

align-items: center;

justify-content: center;

position: relative;

background-color: #000;

.progressItemDiv {

position: absolute;

z-index: 2;

width: 80%;

height: 8px;

display: flex;

.progressItemOutDiv {

height: 100%;

width: 100%;

display: flex;

overflow: hidden;

border-radius: 20rpx;

margin-top: 2rpx;

position: relative;

}

.progressDiv {

position: absolute;

height: 100%;

background-color: #ffffff;

border-radius: 20rpx;

width: 0;

}

.progressBorder {

flex: 1;

border: 2rpx solid #ffffff;

// width: calc(100%);

// height: calc(24rpx * 0.9 - 2rpx - 4rpx);

border-radius: 20rpx;

}

}

.screenBtnFont {

font-size: 40rpx;

font-weight: bold;

color: #ffffff;

position: absolute;

bottom: 20rpx;

left: 20rpx;

}

.safeBottom {

/* #ifdef H5 */

bottom: calc(20rpx + constant(safe-area-inset-bottom));

bottom: calc(20rpx + env(safe-area-inset-bottom));

/* #endif */

}

}

</style>

Tags:

最近发表
标签列表