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

news2024/11/24 7:30:46

如果你想用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

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

HTML元素创建标签

<div id="tag">标签内容</div>

CSS2模型对象CSS2DObject

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

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

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

tag.position.set(50,0,50);

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

// 添加到场景
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。
    在这里插入图片描述
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的影响。
在这里插入图片描述

    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父元素的位置设置。
在这里插入图片描述

// tag.position.set(0, 0, 0);
tag.position.set(-1, 0, 0);
  1. .getWorldPosition()方法计算世界坐标。
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更方便。
const div = document.getElementById('tag');
// HTML元素转化为threejs的CSS2模型对象
const tag = new CSS2DObject(div);
// 标签tag作为mesh子对象,默认受到父对象位置影响
mesh.add(tag);

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

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

// 创建一个CSS2渲染器CSS2DRenderer
const css2Renderer = new CSS2DRenderer();

CSS2Renderer.render()

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

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

CSS2Renderer.setSize()

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

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网页中任何你想放的位置。

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>外面的父元素。

<!-- `<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的尺寸保持一致,不然位置不好对应。

// HTML标签<div id="tag"></div>外面父元素叠加到canvas画布上且重合
css2Renderer.domElement.style.position = 'absolute';
css2Renderer.domElement.style.top = '0px';
<!-- `<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画布鼠标事件的遮挡。
css2Renderer.domElement.style.pointerEvents = 'none';
  1. 设置.style.zIndex,改变css2Renderer.domElement层级,这种方式如果标签层级在下,threejs canvas画布在上,标签被canvas画布遮挡,看不到标签
renderer.domElement.style.zIndex = -1;
css2Renderer.domElement.style.zIndex = 1;

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

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

Canvas尺寸变化(HTML标签)

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

// 画布跟随窗口变化
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标签元素的隐藏状态了。

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

鼠标选中模型添加标签

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

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;",从而解决点击无效的问题。

 <div id='tag' style={{ background: '#FFFFFF', display: 'none' }}>
    <span>标签内容</span>
    <span className='close' style={{ pointerEvents: 'auto' }} onClick={closeEventInfo}>×</span>
</div>
// 关闭事件
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渲染的标签默认保持自身像素值。

// 引入CSS3渲染器CSS3DRenderer和CSS3模型对象CSS3DObject
import { CSS3DObject, CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
// 创建一个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);
// 渲染循环
function render() {
    css3Renderer.render(scene, camera);
    requestAnimationFrame(render);
}
window.onresize = function () {
    // HTML标签css3Renderer.domElement尺寸重新设置
    css3Renderer.setSize(width,height);
};

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

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不可见。

<div id="tag" style="backface-visibility: hidden;">标签内容</div>

批量创建标签

// 需要批量标注的标签数据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画布,不受旋转影响。

// 引入CSS3精灵模型对象CSS3DSprite
import { CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js';
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是更合适的选择‌。

完整示例代码

射线拾取方法参考

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 >;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2061867.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

探秘黑魔术玫瑰:花语与魅力的交织

一、黑魔术玫瑰的花语解读 黑魔术玫瑰的花语丰富而深邃&#xff0c;充满了神秘与诱惑。在许多文化中&#xff0c;其深沉的颜色被赋予了神秘的象征意义&#xff0c;代表着那些尚未被揭示的秘密和未知的领域。它仿佛是隐藏在黑暗中的谜题&#xff0c;吸引着人们去探索和追寻。 禁…

plsql表格怎么显示中文 plsql如何导入表格数据

在Oracle数据库开发中&#xff0c;PL/SQL Developer是一款广泛使用的集成开发环境&#xff08;IDE&#xff09;&#xff0c;它提供了丰富的功能来帮助开发人员高效地进行数据库开发和管理。在使用PL/SQL Developer时&#xff0c;许多用户会遇到表格显示中文的问题&#xff0c;以…

DRF——请求的封装与版本管理

文章目录 django restframework1. 快速上手2. 请求数据的封装3. 版本管理3.1 URL的GET参数传递&#xff08;*&#xff09;3.2 URL路径传递&#xff08;*&#xff09;3.3 请求头传递3.4 二级域名传递3.5 路由的namespace传递 小结 django restframework 快速上手请求的封装版本…

SQL,解析 json

Google BigQuery数据库的data表存储了若干多层的Json串&#xff0c;其中一条形如&#xff1a; [{"active":true,"key":"key1","values":[{"active":true,"value":"value1"}]},{"active":tru…

如何在分布式环境中实现高可靠性分布式锁

目录 一、简单了解分布式锁 &#xff08;一&#xff09;分布式锁&#xff1a;应对分布式环境的同步挑战 &#xff08;二&#xff09;分布式锁的实现方式 &#xff08;三&#xff09;分布式锁的使用场景 &#xff08;四&#xff09;分布式锁需满足的特点 二、Redis 实现分…

Golang | Leetcode Golang题解之第363题矩形区域不超过K的最大数值和

题目&#xff1a; 题解&#xff1a; import "math/rand"type node struct {ch [2]*nodepriority intval int }func (o *node) cmp(b int) int {switch {case b < o.val:return 0case b > o.val:return 1default:return -1} }func (o *node) rotate…

算法日记day 44(动归之编辑距离|回文字串|最长回文子序列)

一、编辑距离 题目&#xff1a; 给你两个单词 word1 和 word2&#xff0c; 请返回将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作&#xff1a; 插入一个字符删除一个字符替换一个字符 示例 1&#xff1a; 输入&#xff1a;word1 "…

WEB渗透免杀篇-cshot远程shellcode

往期文章 WEB渗透免杀篇-免杀工具全集-CSDN博客 WEB渗透免杀篇-加载器免杀-CSDN博客 WEB渗透免杀篇-分块免杀-CSDN博客 WEB渗透免杀篇-Powershell免杀-CSDN博客 WEB渗透免杀篇-Python源码免杀-CSDN博客 WEB渗透免杀篇-C#源码免杀-CSDN博客 WEB渗透免杀篇-MSFshellcode免杀…

基于.net技术的物业管理系统需求分析与设计

系统需求分析 2.1 整体需求概述 根据某XXXXXXXX管理公司实际业务调研分析&#xff0c;可将其系统需求划分为7个部分&#xff1a;基础信息维护、网上报修、权限管理、动力消耗、物料管理、收费管理、报表分析。 2.1.1 基础信息维护 基础信息维护包括对以下业务基础数据的采集…

linux驱动——设备树

1&#xff1a;初识设备树 1.1 什么是设备树&#xff0c;设备树的意义 设备树&#xff08;Device Tree&#xff09;是 Linux 内核中用于描述硬件设备的一种数据结构。它为操作系统提供了一种抽象的方法&#xff0c;使其能够识别和配置硬件设备&#xff0c;而无需将硬件细节硬编…

QT-五子棋游戏

QT-五子棋游戏 一、演示效果二、核心代码三、下载链接 一、演示效果 二、核心代码 #include "GameModel.h" #include <time.h> #include <stdlib.h>GameModel::GameModel(){}void GameModel::startGame(GameType type){gameType type;//初始化棋盤game…

【备忘录模式】设计模式系列:掌握状态回溯的艺术(设计详解)

文章目录 备忘录设计模式详解引言1. 设计模式概述2. 备忘录模式的基本概念2.1 备忘录模式的定义2.2 备忘录模式的关键角色 3. 备忘录模式的实现原理3.1 备忘录模式的工作流程3.2 模式的优缺点分析3.3 与其他模式的对比 4. 实际案例分析4.1 游戏状态保存与恢复4.2 文档编辑器撤销…

19529 照明灯安装

### 详细分析 这个问题可以通过二分查找和贪心算法来解决。我们需要找到一个最大值&#xff0c;使得在这个最大值下&#xff0c;能够在给定的坐标上安装 k 个照明灯&#xff0c;并且相邻的照明灯之间的距离至少为这个最大值。 ### 思路 1. **排序**&#xff1a;首先对给定的…

S3C2440中断处理

一、中断处理机制概述 中断是CPU在执行程序过程中&#xff0c;遇到急需处理的事件时&#xff0c;暂时停止当前程序的执行&#xff0c;转而执行处理该事件的中断服务程序&#xff0c;并在处理完毕后返回原程序继续执行的过程。S3C2440提供了丰富的中断源&#xff0c;包括内部中…

微信小程序:开发工具修改js编译后还是旧的js逻辑

1、清理所有缓存&#xff0c;重新导入项目 2、语法存在问题无法编译,导致内存堆积&#xff0c;无法自动编译 3、npm 存在问题&#xff0c;可以重新构建 4、有时候编译器也没报错都是一切正常&#xff0c;但是编译后依然不是最新。这个时候需要考虑下电脑是否存在问题&#xff0…

使用gitee存储项目

gitee地址&#xff1a;Gitee - 基于 Git 的代码托管和研发协作平台 创建gitee远程仓库 将远程仓库内容拉取到本地仓库 复制下面这个地址 通过小乌龟便捷推送拉取代码&#xff1a;https://blog.csdn.net/m0_65520060/article/details/140091437

Ubuntu | 解决 VMware 中 Ubuntu 虚拟机磁盘空间不足的问题

目录 一、存在的问题二、解决的步骤第一步&#xff1a;扩展磁盘空间第二步&#xff1a;查看磁盘空间使用情况第三步&#xff1a;安装分区工具第四步&#xff1a;启动分区工具第五步&#xff1a;修改挂载文件夹的读写权限第六步&#xff1a;扩展文件系统大小第七步&#xff1a;验…

Prometheus2:被监控机器安装node_exporter与配置

1. 下载node_exporter [rootlocalhost ~]# wget https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz 2. 解压缩 [rootlocalhost ~]# tar -zxvf node_exporter-1.8.2.linux-amd64.tar.gz 3. 复制到/usrl/local路…

sed命令用法与案例

在Linux操作系统中&#xff0c;sed&#xff08;stream editor&#xff09;是一种功能强大的文本处理工具&#xff0c;用于执行文本的查找、替换、删除、新增等操作。sed命令以其简洁的语法和高效的执行速度&#xff0c;在自动化脚本和文本处理中扮演着重要角色。本文将探讨sed命…

探索串行通信的奥秘:Python中的pyserial库

文章目录 探索串行通信的奥秘&#xff1a;Python中的pyserial库背景&#xff1a;为何选择pyserial&#xff1f;pyserial是什么&#xff1f;如何安装pyserial&#xff1f;pyserial的五个简单函数场景应用&#xff1a;pyserial在实际中的使用常见bug及解决方案总结 探索串行通信的…