3D场景标注标签信息,three.js CSS 2D渲染器CSS2DRenderer、CSS 3D渲染器CSS3DRenderer(结合react)

如果你想用HTML元素作为标签标注三维场景中模型信息,需要考虑定位的问题。比如一个模型,在代码中你可以知道它的局部坐标或世界坐标xyz,但是你并不知道渲染后在canvas画布上位置,距离web页面顶部top和左侧的像素px值。自己写代码把世界坐标xyz,转化为像素px表示屏幕坐标,比较麻烦,不过threejs扩展库CSS2DRenderer.js可以帮助你实现坐标转化,给HTML元素标签定位,下面给大家演示如何实现。

CSS2DRenderer

‌CSS2DRenderer是Three.js的扩展库,用于在 WebGL 场景中渲染 HTML 元素。它允许开发者将DOM元素(如<div><span>等)直接集成到Three.js的3D场景中,从而实现2D元素与3D物体的交互和结合。总的来说,CSS2DRenderer为Three.js提供了强大的功能,使得在3D环境中集成2D界面元素成为可能,这对于创建具有丰富视觉效果和交互性的3D应用非常有用‌。

CSS2DRenderer的主要特点是它支持将HTML元素作为3D对象进行处理,这些HTML元素可以通过CSS进行样式化,并且可以在Three.js的渲染循环中进行更新和动画处理。

使用CSS2DRenderer的基本步骤

引入扩展库CSS2DRenderer.js

threejs扩展库CSS2DRenderer.js提供了两个类CSS2渲染器CSS2DRenderer、CSS2模型对象CSS2DObject

javascript 复制代码
// 引入CSS2渲染器CSS2DRenderer和CSS2模型对象CSS2DObject
import { CSS2DObject, CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer.js';

HTML元素创建标签

html 复制代码
<div id="tag">标签内容</div>

CSS2模型对象CSS2DObject

将HTML元素(如<div>标签)添加到场景中,这些元素将被视为CSS2DObject实例,通过CSS2DObject类把一个HTML元素转化为一个类似threejs网格模型的对象,可以像操作其他Three.js对象一样对它们进行变换(如位置、旋转等)或者添加到场景中。

javascript 复制代码
const div = document.getElementById('tag');
// HTML元素转化为threejs的CSS2模型对象
const tag = new CSS2DObject(div);

通过.position属性设置标签模型对象的xyz坐标。

javascript 复制代码
tag.position.set(50,0,50);

把HTML元素对应的CSS2模型对象添加到其它模型对象或三维场景中。

javascript 复制代码
// 添加到场景
scene.add(tag);
// 添加到组
const group = new THREE.Group();
group.add(tag);
// 添加几何体上
const geometry = new THREE.BoxGeometry(50, 50, 50);
const material = new THREE.MeshBasicMaterial({
    color: 0xfaf33a,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.add(tag);

标签位置的不同设置方式

  1. CSS2模型标签对象位置和要标注的mesh放在同一个位置,这样HTML标签就可以标注mesh。
javascript 复制代码
const group = new THREE.Group();
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({
    color: 0xfaf33a,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(0, 0, 0);
const div = document.getElementById('tag');
// HTML元素转化为threejs的CSS2模型对象
const tag = new CSS2DObject(div);
tag.position.set(0, 0, 0);
group.add(mesh, tag);

如果需要的mesh有多个父对象,且都有自己的位置属性.position,设置mesh标签对象位置CSS2DObject.position的时候,就需要考虑mesh父对象的位置对mesh的影响。

javascript 复制代码
    let group = new THREE.Group();
    const geometry = new THREE.BoxGeometry(1, 1, 1);
    const material = new THREE.MeshBasicMaterial({
        color: 0xfaf33a,
    });
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(0, 0, 0);
    // mesh设置一个父对象meshGroup
    const meshGroup = new THREE.Group();
    meshGroup.add(mesh);
    
    // mesh位置受到父对象局部坐标.positionn影响
    meshGroup.position.x = -1;
    
    const div = document.getElementById('tag');
    // HTML元素转化为threejs的CSS2模型对象
    const tag = new CSS2DObject(div);
    tag.position.set(0, 0, 0);
    group.add(meshGroup, tag);

上面可以看出,不考虑mesh父元素.position对mesh的影响,设置标签位置标签偏离。此时标签的位置设置应根据Mesh父元素的位置设置。

javascript 复制代码
// tag.position.set(0, 0, 0);
tag.position.set(-1, 0, 0);
  1. .getWorldPosition()方法计算世界坐标。
javascript 复制代码
mesh.position.set(0, 0, 0);
// mesh设置一个父对象meshGroup
const meshGroup = new THREE.Group();
meshGroup.add(mesh);
// mesh位置受到父对象局部坐标.positionn影响
meshGroup.position.x = -1;

const tag = new CSS2DObject(div);
const worldPosition = new THREE.Vector3();
// 获取mesh的世界坐标(meshGroup.position和mesh.position累加结果)
mesh.getWorldPosition(worldPosition);
// mesh世界坐标复制给tag
tag.position.copy(worldPosition);

const group = new THREE.Group();
// 最后meshGroup和tag放在同一个父对象中即可
group.add(meshGroup, tag);
  1. CSS2模型对象作为mesh子对象。无论mesh有多少个父对象,CSS2模型对象作为mesh子对象,可以直接继承mesh的世界坐标,相比通过.getWorldPosition()方法获取世界坐标,再设置标签模型位置CSS2DObject.position更方便。
javascript 复制代码
const div = document.getElementById('tag');
// HTML元素转化为threejs的CSS2模型对象
const tag = new CSS2DObject(div);
// 标签tag作为mesh子对象,默认受到父对象位置影响
mesh.add(tag);

一个模型对象,不管是一个mesh,还是多个mesh组成的模型,本身是有尺寸的,如果你把标签模型对象CSS2DObject作为该模型对象的子元素,标签默认是标注在模型的局部坐标系坐标原点。

javascript 复制代码
loader.load("小人.glb", function (gltf) {
    const obj = gltf.scene.getObjectByName('Bai');
    const div = document.getElementById('tag');
    // HTML元素转化为threejs的CSS2模型对象
    const tag = new CSS2DObject(div);
    // 标签tag作为obj子对象
    obj.add(tag);
})

CSS2渲染器CSS2DRenderer

CSS2渲染器CSS2DRenderer和常用的WebGL渲染器WebGLRenderer一样都是渲染器,只是渲染模型对象不同,WebGLRenderer主要是渲染threejs自身的网格、点、线等模型,CSS2DRenderer用来渲染HTML元素标签对应的CSS2模型对象CSS2DObject

javascript 复制代码
// 创建一个CSS2渲染器CSS2DRenderer
const css2Renderer = new CSS2DRenderer();

CSS2Renderer.render()

CSS2渲染器CSS2DRenderer和WebGL渲染器WebGLRenderer虽然不同,但是有些属性和方法是相似的,比如.domElement.setSize().render()

javascript 复制代码
// 用法和webgl渲染器渲染方法类似
css2Renderer.render(scene, camera);

CSS2Renderer.setSize()

设置CSS2Renderer.render()渲染输出标签的尺寸范围,一般和threejs canvas画布宽高度一致即可。

javascript 复制代码
const width = window.innerWidth; // canvas画布宽度 
const height = window.innerHeight; // canvas画布高度
renderer.setSize(width, height); // 设置渲染区域尺寸
css2Renderer.setSize(width, height);

CSS2Renderer.domElement

CSS2Renderer.render()渲染会输出标签对应的HTML元素,也就是css2Renderer.domElement,你可以插入到web网页中任何你想放的位置。

javascript 复制代码
document.body.appendChild(css2Renderer.domElement);
// 放在指定元素中
document.getElementById('box').appendChild(css2Renderer.domElement);

threejs执行css2Renderer.render()之后,你打开浏览器控制台元素选项,找到你创建的HTML标签<div id="tag">标签内容</div>,你可以发现<div id="tag"></div>外面多了一层div父元素,有两个DOM(一个存放三维模型,一个存放CSS2D对象),CSS2Renderer.domElement是一个HTML元素,对应的就是<div id="tag"></div>外面的父元素。

html 复制代码
<!-- `<div id="tag"></div>`外面多了一层div父元素 -->
<div style="overflow: hidden; width: 1087px; height: 1069px;">
    <div id="tag">标签内容</div>
</div>

还可以发现,你创建的HTML标签<div id="tag"></div>不在原来的位置了,其实是被CSS2Renderer改变了位置。css2Renderer.render()渲染HTML元素对应的CSS2模型对象,本质上就是根据CSS2模型对象的xyz世界坐标,计算HTML标签元素在canvas画布上的屏幕像素坐标位置。

CSS2Renderer.domElement重新定位

CSS2Renderer.domElement重新定位,将外面div父元素重新定位,叠加到canvas画布上,与canvas画布重合即可,可以看到HTML标签的标注效果。注意这里css2Renderer尺寸一定要和渲染器canvas的尺寸保持一致,不然位置不好对应。

javascript 复制代码
// HTML标签<div id="tag"></div>外面父元素叠加到canvas画布上且重合
css2Renderer.domElement.style.position = 'absolute';
css2Renderer.domElement.style.top = '0px';
html 复制代码
<!-- `<div id="tag"></div>`外面多了一层div父元素 -->
<div style="overflow: hidden; width: 1087px; height: 1069px; position: absolute; top: 0px;">
    <div id="tag">标签内容</div>
</div>

HTML标签遮挡Canvas画布事件

HTML元素标签<div id="tag"></div>外面div父元素遮挡了Canvas画布鼠标事件,会造成相机控件OrbitControls的旋转、缩放等操作无效,也有可能会影响你的射线拾取,等等任何与canvas画布有关的鼠标事件都有可能受到影响。

  1. 设置.style.pointerEvents = none,就可以解决HTML元素标签对threejs canvas画布鼠标事件的遮挡。
javascript 复制代码
css2Renderer.domElement.style.pointerEvents = 'none';
  1. 设置.style.zIndex,改变css2Renderer.domElement层级,这种方式如果标签层级在下,threejs canvas画布在上,标签被canvas画布遮挡,看不到标签
javascript 复制代码
renderer.domElement.style.zIndex = -1;
css2Renderer.domElement.style.zIndex = 1;

相机控件操作失效还可通过设置OrbitControls监听的HTML元素为css2Renderer.domElement解决。

javascript 复制代码
controls = new OrbitControls(camera, css2Renderer.domElement);

Canvas尺寸变化(HTML标签)

canvas画布完全填充浏览器文档区域,如果窗口尺寸变化了,通过renderer.setSize()设置canvas画布尺寸,HTML标签相关的CSS渲染器代码也要同步设置css2Renderer.setSize()。执行css2Renderer.setSize()设置CSS2渲染器输出的HTML标签.domElement的尺寸,保持和canvas画布尺寸一样。

javascript 复制代码
// 画布跟随窗口变化
window.onresize = function () {
    const width = window.innerWidth;
    const height = window.innerHeight;
    // cnavas画布宽高度重新设置
    renderer.setSize(width,height);
    // HTML标签css2Renderer.domElement尺寸重新设置
    css2Renderer.setSize(width,height);
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
};

HTML标签渲染前隐藏

在CSS2渲染器渲染HTML标签,重新定位标签之前,threejs执行代码和加载gltf模型也是需要时间的,这时候标签对应的HTML、CSS代码会显示在web页上面。可以先把标签隐藏display: none;,等gltf模型加载完成,HTML元素转化CSS2模型对象以后,再取消HTML隐藏状态,CSS2渲染器默认会把标签设置为display: block;,这样就不用自己代码恢复HTML标签元素的隐藏状态了。

html 复制代码
<!-- CSS2渲染器渲染器之前,隐藏标签 -->
<div id="tag" style="display: none;"><div>

鼠标选中模型添加标签

当发生鼠标事件,如果射线拾取到模型对象,就把标签做为选中模型的子对象,或作为选中模型对应标注点空对象的子对象。

javascript 复制代码
let modelObj = null;
addEventListener('click', function (event) {
    // ...射线拾取的代码
    // 射线交叉计算拾取模型
    const intersects = raycaster.intersectObjects(scene);
    if (intersects.length > 0) {
        // tag会标注在intersects[0].object.parent模型的局部坐标系原点位置
        intersects[0].object.ancestors.add(tag);
        modelObj = intersects[0].object.parent; // 被选中模型
    }else{
        //把原来选中模型对应的标签隐藏
        if(modelObj){
            modelObj.remove(tag); // 从场景移除
        }
    }
})

单击关闭HTML标签

前面我们了解到,如果你的项目要求三维场景中添加标签时,不能影响canvas画布的事件,必须设置css2Renderer.domElement.style.pointerEvents = 'none',此刻你单击按钮去关闭HTML元素标签,会发现无效,可以考虑把标签的子元素关闭按钮,单独设置.style.pointerEvents = 'auto'或者style="pointer-events: auto;",从而解决点击无效的问题。

html 复制代码
 <div id='tag' style={{ background: '#FFFFFF', display: 'none' }}>
    <span>标签内容</span>
    <span className='close' style={{ pointerEvents: 'auto' }} onClick={closeEventInfo}>×</span>
</div>
javascript 复制代码
// 关闭事件
function closeEventInfo() {
    // 获取标签对象
    let tagObj = scene.getObjectByName('tag');
    if (tagObj) {
        let tagParent = tagObj.parent;
        // 将标签从父元素中移除
        tagParent.remove(tagObj);
    }
}

CSS3DRenderer渲染HTML标签

CSS3渲染器CSS3DRenderer和CSS2渲染器CSS2DRenderer整体使用流程基本相同,只是在HTML标签渲染效果方面不同,比如CSS3渲染的标签会跟着场景相机同步缩放,而CSS2渲染的标签默认保持自身像素值。

javascript 复制代码
// 引入CSS3渲染器CSS3DRenderer和CSS3模型对象CSS3DObject
import { CSS3DObject, CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
javascript 复制代码
// 创建一个CSS3渲染器CSS3DRenderer
const css3Renderer = new CSS3DRenderer();
css3Renderer.setSize(width, height);
// HTML标签<div id="tag"></div>外面父元素叠加到canvas画布上且重合
css3Renderer.domElement.style.position = 'absolute';
css3Renderer.domElement.style.top = '0px';
//设置.pointerEvents=none,解决HTML元素标签对threejs canvas画布鼠标事件的遮挡
css3Renderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(css3Renderer.domElement);
javascript 复制代码
// 渲染循环
function render() {
    css3Renderer.render(scene, camera);
    requestAnimationFrame(render);
}
javascript 复制代码
window.onresize = function () {
    // HTML标签css3Renderer.domElement尺寸重新设置
    css3Renderer.setSize(width,height);
};

通过CSS3DObject类,可以把一个HTML元素转化为一个CSS3模型对象,就像threejs的网格模型一样,可以添加到场景中,可以设置位置,可以作为其它模型对象的子对象。

javascript 复制代码
const div = document.getElementById('tag');
// HTML元素转化为threejs的CSS3模型对象
const tag = new CSS3DObject(div);
// 标签tag作为mesh子对象,默认标注在模型局部坐标系坐标原点
mesh.add(tag);
// 相对父对象局部坐标原点偏移100
tag.position.y += 100;
// 如果标签覆盖区域过大,可以适当缩小,缩放标签尺寸
tag.scale.set(0.5, 0.5, 1);

CSS3模型对象CSS3DObject渲染结果,就像一个矩形平面网格模型一样。你通过相机控件OrbitControls旋转、缩放三维场景,CSS3模型对象CSS3DObject跟着旋转、缩放。旋转过程中HTML元素标签的正反面都可以看到,但是旋转到背面是一个对称的效果。

禁止CSS3DObject标签对应HTMl元素背面显示

可是禁止展示对应HTML元素的背面,直观感受就是当相机控件OrbitControls旋转到CSS3模型对象CSS3DObject的背面时,CSS3模型对象CSS3DObject不可见。

html 复制代码
<div id="tag" style="backface-visibility: hidden;">标签内容</div>

批量创建标签

javascript 复制代码
// 需要批量标注的标签数据arr
const arr = ['设备A','设备B','停车场'];
for (let i = 0; i < arr.length; i++) {
    // 注意是多个标签,需要克隆复制一份
    const div = document.getElementById('tag').cloneNode();
    div.innerHTML = arr[i]; // 标签数据填写
    // HTML元素转化为threejs的CSS3对象
    const tag = new CSS3DObject(div);
    div.style.pointerEvents = 'none'; // 避免标签遮挡canvas鼠标事件
    // obj是建模软件中创建的一个空对象
    const obj = gltf.scene.getObjectByName(arr[i]);
    // tag会标注在空对象obj对应的位置
    obj.add(tag);

    tag.scale.set(0.1, 0.1, 1); // 适当缩放模型标签
    tag.position.y = 40/2 * 0.1;// 标签底部和空对象标注点重合:偏移高度像素值一半*缩放比例
}

CSS3精灵模型CSS3DSprite

CSS3对象模型CSS3DObject渲染效果类似矩形平面网格模型Mesh。CSS3精灵模型CSS3DSprite渲染效果精灵模型对象Sprite。CSS3精灵模型CSS3DSprite对应的HTML标签,可以跟着场景缩放,位置可以跟着场景旋转,但是自身的姿态角度始终平行于canvas画布,不受旋转影响。

javascript 复制代码
// 引入CSS3精灵模型对象CSS3DSprite
import { CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js';
javascript 复制代码
const div = document.getElementById('tag');
// HTML元素转化为threejs的CSS3模型对象
const tag = new CSS3DSprite(div);
// 标签tag作为mesh子对象,默认标注在模型局部坐标系坐标原点
const geometry = THREE.BoxGeometry(100, 100, 100);
geometry.translate(0, 50, 0);
const material = new THREE.MeshBasicMaterial({
    color: 0xfaf33a,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.add(tag);
// 相对父对象局部坐标原点偏移100,标签高16,标签偏移108刚好标签的底部在几何体的顶部
tag.position.y += 108;

CSS2DRenderer和CSS3DRenderer的区别

从上面可以看出CSS3DRenderer和CSS2DRenderer的主要区别在于它们面向摄像机的方向、场景缩放时的表现、是否被模型遮挡,以及如何处理DOM事件。

  • CSS2DRenderer ‌:主要用于在Three.js场景中渲染2D元素。它允许开发者在3D空间中放置和操作2D对象,这些对象的大小和位置不会随着摄像机的视角的变化而改变,保持固定大小,始终面向屏幕。CSS2DRenderer基于Web技术,使用HTML元素和CSS样式来创建2D对象,这些对象可以与3D对象进行交互,如点击事件等。
  • CSS3DRenderer ‌:用于在Three.js场景中渲染具有深度信息的3D对象。它允许开发者创建和操作真正的3D对象,这些对象的大小和位置会随着摄像机的视角的变化而改变,提供更加立体和逼真的效果。CSS3DRenderer可以处理更复杂的3D模型和动画,适用于需要深度感知和立体效果的场景‌。

总的来说,CSS2DRenderer适合添加简单的2D元素到3D场景中,如文字标签和图像,而CSS3DRenderer则更适合处理复杂的3D模型和动画。选择使用哪种Renderer取决于项目的具体需求和预期的视觉效果‌,如果元素的大小需要随摄像机的位置和角度变化,或者希望元素能够响应摄像机的缩放,那么CSS3DRenderer是合适的选择。如果元素的大小需要保持固定,或者希望元素能够像传统2D图形一样不受摄像机影响,那么CSS2DRenderer是更合适的选择‌。

完整示例代码

射线拾取方法参考

javascript 复制代码
import React, { useRef, useEffect } from 'react';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
// 引入CSS2渲染器CSS2DRenderer和CSS2模型对象CSS2DObject
import { CSS2DObject, CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
// 引入CSS3渲染器CSS3DRenderer和CSS3模型对象CSS3DObject
import { CSS3DObject, CSS3DSprite, CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
import { getCanvasIntersects } from '@/common/three/index.js'; // three自定义公共方法射线穿透

let scene, camera, renderer, css2Renderer, css3Renderer, controls, tag;

export default function Css2DOr3DRenderer() {
    const box = useRef(); // canvas盒子
    // 加载模型
    function setGltfModel() {
        // 导入GlTF模型
        let gltfLoader = new GLTFLoader();
        gltfLoader.load('模型.glb', (gltf) => {
            // 添加标签
            // create2DTag(gltf.scene);
            create3DTag(gltf.scene);
            scene.add(gltf.scene);
        });
    }
    // 添加2D标签
    function create2DTag(modelObj) {
        // 添加标签
        if (!tag) {
            const tagDom = document.getElementById('tag');
            // HTML元素转化为threejs的CSS2模型对象
            tag = new CSS2DObject(tagDom);
            tag.name = 'tag';
            tag.position.y = 100;
        }
        // 避免重复添加
        if (modelObj.getObjectByName('tag')) {
            return;
        } else {
            modelObj.add(tag);
        }
    }
    // 添加3D标签
    function create3DTag(modelObj) {
        // 添加标签
        if (!tag) {
            const tagDom = document.getElementById('tag');
            // 避免标签遮挡canvas鼠标事件
            tagDom.style.pointerEvents = 'none';
            // HTML元素转化为threejs的CSS3模型对象
            tag = new CSS3DSprite(tagDom);
            // tag = new CSS3DObject(tagDom);
            tag.name = 'tag';
            tag.position.y += 10;
            tag.scale.set(0.5, 0.5, 1); // 缩放标签尺寸
        }
        // 避免重复添加
        if (modelObj.getObjectByName('tag')) {
            return;
        } else {
            modelObj.add(tag);
        }
    }
    // 关闭标签事件
    function closeEventInfo() {
        // 获取标签对象
        let tagObj = scene.getObjectByName('tag'); 
        if (tagObj) { 
            let tagParent = tagObj.parent; 
            // 将标签从父元素中移除 
            tagParent.remove(tagObj); 
        }
    }
    // 渲染动画
    function renderFn() {
        requestAnimationFrame(renderFn);
        controls.update();
        // 用相机渲染一个场景
        renderer && renderer.render(scene, camera);
        // 渲染HTML标签对应的CSS2DObject模型对象
        // css2Renderer && css2Renderer.render(scene, camera);
        // 渲染HTML标签对应的CSS3DObject模型对象
        css3Renderer && css3Renderer.render(scene, camera);
    }
    // 监听窗体变化、自适应窗体事件
    function onWindowResize() {
        let width = box.current.offsetWidth;
        let height = box.current.offsetHeight;
        camera.aspect = width / height;
        // 更新相机投影矩阵,在相机任何参数被改变以后必须被调用
        camera.updateProjectionMatrix();
        renderer.setSize(width, height);
        // css2Renderer.setSize(width, height); // 设置css2D渲染区域尺寸
        css3Renderer.setSize(width, height); // 设置css3D渲染区域尺寸
    }
    // 监听事件 窗体监听、点击事件监听
    useEffect(() => {
        // 监听窗体变化
        window.addEventListener('resize', onWindowResize, false);
        // 监听点击事件
        box.current.addEventListener('click', (event) => {
            let selectObj = getCanvasIntersects(event, box.current, camera, scene);
            if (selectObj[0]) {
                let modelObj = selectObj[0].object.parent;
                create2DTag(modelObj);
                // create3DTag(modelObj);
            } else {
                closeEventInfo();
            }
        }, false);
    }, []);

    // 初始化环境、灯光、相机、渲染器
    useEffect(() => {
        scene = new THREE.Scene();
        // 添加光源
        const ambitlight = new THREE.AmbientLight(0x404040);
        scene.add(ambitlight);
        const sunlight = new THREE.DirectionalLight(0xffffff); 
        sunlight.position.set(-20, 1, 1); scene.add(sunlight);
        // 加载模型
        setGltfModel();
        let axisHelper = new THREE.AxesHelper(100);
        scene.add(axisHelper); // 坐标辅助线加入到场景中

        // 获取宽高设置相机和渲染区域大小
        let width = box.current.offsetWidth;
        let height = box.current.offsetHeight;
        let k = width / height;
        // 投影相机
        camera = new THREE.PerspectiveCamera(45, k, 1, 100000);
        camera.position.set(32, 122, 580);
        camera.lookAt(scene.position);

        // 创建一个webGL对象
        renderer = new THREE.WebGLRenderer({
            //增加下面两个属性,可以抗锯齿
            antialias: true,
            alpha: true
        });
        renderer.setSize(width, height); // 设置渲染区域尺寸
        renderer.setClearColor(0x333333, 1); // 设置颜色透明度
        renderer.outputEncoding = THREE.sRGBEncoding; // 解决纹理贴图颜色偏差
        box.current.appendChild(renderer.domElement);
        
        // 创建一个CSS2渲染器CSS2DRenderer
        // css2Renderer = new CSS2DRenderer(); 
        // css2Renderer.setSize(width, height);
        // // HTML标签<div id="tag"></div>外面父元素叠加到canvas画布上且重合
        // css2Renderer.domElement.style.position = 'absolute';
        // css2Renderer.domElement.style.top = '0px';
        // css2Renderer.domElement.style.pointerEvents = 'none'; // 避免标签遮挡canvas鼠标事件
        // box.current.appendChild(css2Renderer.domElement);
        
        // 创建一个CSS3渲染器CSS3DRenderer
        css3Renderer = new CSS3DRenderer();
        css3Renderer.setSize(width, height);
        // // HTML标签<div id="tag"></div>外面父元素叠加到canvas画布上且重合
        css3Renderer.domElement.style.position = 'absolute';
        css3Renderer.domElement.style.top = '0px';
        css3Renderer.domElement.style.pointerEvents = 'none'; // 避免标签遮挡canvas鼠标事件
        box.current.appendChild(css3Renderer.domElement);

        // 监听鼠标事件
        controls = new OrbitControls(camera, renderer.domElement);
        // 渲染
        renderFn();
    }, []);
    useEffect(() => {
        return () => {
            // 清除数据
            scene = null;
            camera = null;
            renderer = null;
            css2Renderer = null;
            css3Renderer = null;
            controls = null;
            tag = null;
        }
    }, []);

    return <div className='ui_container_box'>
        <div style={{ position: 'relative', width: '100%', height: '100%' }} ref={box}></div>
        <div id='tag' style={{ background: '#FFFFFF', display: 'none' }}>
            <span>标签内容</span>
            <span className='close' style={{ pointerEvents: 'auto' }} onClick={closeEventInfo}>×</span>
        </div>
    </div >;
}
相关推荐
yqcoder17 分钟前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
会发光的猪。1 小时前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js
天下代码客1 小时前
【vue】vue中.sync修饰符如何使用--详细代码对比
前端·javascript·vue.js
Domain-zhuo2 小时前
什么是JavaScript原型链?
开发语言·前端·javascript·jvm·ecmascript·原型模式
小丁爱养花2 小时前
前端三剑客(三):JavaScript
开发语言·前端·javascript
码农六六2 小时前
vue3封装Element Plus table表格组件
javascript·vue.js·elementui
徐同保2 小时前
el-table 多选改成单选
javascript·vue.js·elementui
快乐小土豆~~2 小时前
el-input绑定点击回车事件意外触发页面刷新
javascript·vue.js·elementui
建群新人小猿3 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
爱上语文3 小时前
HTML和CSS 表单、表格练习
前端·css·html