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
。
// 引入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);
标签位置的不同设置方式
- 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设置一个父对象meshGroupconst 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);
.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);
- 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画布有关的鼠标事件都有可能受到影响。
- 设置
.style.pointerEvents = none
,就可以解决HTML元素标签对threejs canvas画布鼠标事件的遮挡。
css2Renderer.domElement.style.pointerEvents = 'none';
- 设置
.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渲染器CSS3DRenderercss3Renderer = 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 >;
}
相关文章:
![](https://i-blog.csdnimg.cn/direct/6e0ecdbb0d4e4981bcf8f5b70649de4d.png#pic_center)
3D场景标注标签信息,three.js CSS 2D渲染器CSS2DRenderer、CSS 3D渲染器CSS3DRenderer(结合react)
如果你想用HTML元素作为标签标注三维场景中模型信息,需要考虑定位的问题。比如一个模型,在代码中你可以知道它的局部坐标或世界坐标xyz,但是你并不知道渲染后在canvas画布上位置,距离web页面顶部top和左侧的像素px值。自己写代码把…...
![](https://www.ngui.cc/images/no-images.jpg)
C++参悟-单例模式
单例模式 一、概述1. 特点2. 实现方式3. 应用场景 二、实现代码1. 静态局部变量的懒汉单例2. 加锁的懒汉式单例3. 使用 C11 中的 std::call_one 的懒汉单例4. 饿汉式单例 一、概述 这里记录一下单例模式的最常用使用,单例模式(Single Pattern࿰…...
![](https://i-blog.csdnimg.cn/direct/06a56f672d92401c96bfe04a638a23eb.png)
【题解】—— LeetCode一周小结32
🌟欢迎来到 我的博客 —— 探索技术的无限可能! 🌟博客的简介(文章目录) 【题解】—— 每日一道题目栏 上接:【题解】—— LeetCode一周小结31 5.不含连续1的非负整数 题目链接:600. 不含连续…...
![](https://img-blog.csdnimg.cn/img_convert/c3bd1ab9f5227f39c655193d2076ec90.jpeg)
详解线索分层的目的、维度与创新实践
线索分层是一个系统性的过程,旨在更有效地管理、跟踪和利用线索资源。这一过程可以借鉴多种策略和方法,特别是在用户运营和市场营销中。 1、线索分层的目的 线索分层的主要目的是根据线索的不同特征或成熟度,将其分类管理,以便更…...
![](https://www.ngui.cc/images/no-images.jpg)
于8月21号的回顾
傍晚的日落和逐渐深邃的夜,驱散了白天的极致闷热。倦怠和疲惫充斥着大脑,喧嚣的浮沉又在耳边轰鸣。 我不曾想到,再次打开博客已经是两年后的今天了。手指轻轻滑过鼠标,博客的页面缓缓加载,那些被时间尘封的记忆瞬间涌…...
![](https://www.ngui.cc/images/no-images.jpg)
Abstract Class抽象类
抽象类(Abstract Class)在面向对象编程中是一种特殊的类,它不能被实例化,即不能创建该类的对象。抽象类主要用于定义一组接口(即方法),这些方法的具体实现由子类来完成。抽象类通常用于表示一种…...
![](https://i-blog.csdnimg.cn/direct/8f769457fe5a4072825a9a2ae835ef9f.png)
webrtc ns 降噪之粉红噪声参数推导
webrtc中降噪中,前50帧需要进行简单噪声估计,使用白噪声和粉红噪声模型估算。 首先我们 复习 有色噪声(包含白噪声)的一般模型: S(f) 是频率 f 处的功率谱密度。f是频率。α 是一个频谱指数,通常在1左右。…...
![](https://i-blog.csdnimg.cn/direct/e553a19dfa1f408fa63055cb632cad4d.png)
IO进程线程8月21日
1,思维导图 2,登录 #ifndef __LOG_H__ #define __LOG_H__ #include<myhead.h> typedef struct {char name[20];char pwd[20]; }str;int regist();int login(); #endif#include"log.h" int login() {char a[20]"\n";str p,s;…...
![](https://i-blog.csdnimg.cn/direct/9fc2f7645ba14733be8c5be738811b77.png)
Web安全:SqlMap工具
一、简介 sqlmap 是一款开源的渗透测试工具,可以自动化进行SQL注入的检测、利用,并能接管数据库服务器。它具有功能强大的检测引擎,为渗透测试人员提供了许多专业的功能并且可以进行组合,其中包括数据库指纹识别、数据读取和访问底层文件系统…...
![](https://img-blog.csdnimg.cn/ac2a6fa1a01045b3b9f0ba9c5882aab6.jpg)
用手机写一本电子书
第1步、进入Andi.cn网站 第2步、点击登录,注册用户 第3步、点击去创作,进入创作页面 第4步、点击右下角的小笔,写一篇文章 第5步、下翻,点击提交按钮 第6步、再写一篇文章 第7步、点击栏目设计 第8步、进入栏目设计,点…...
![](https://i-blog.csdnimg.cn/direct/98a753045eab4db69206f41285920a21.png)
【网络编程】基于UDP的TFTP文件传输
1)tftp协议概述 简单文件传输协议,适用于在网络上进行文件传输的一套标准协议,使用UDP传输 特点: 是应用层协议 基于UDP协议实现 数据传输模式 octet:二进制模式(常用) mail:已经不再…...
![](https://www.ngui.cc/images/no-images.jpg)
Vue 3 + Pinia 实现网页刷新功能
概述 在现代 Web 开发中,保持用户界面的动态性和响应性至关重要。当用户触发某些操作时,例如点击按钮或者完成表单提交,我们往往需要刷新页面的一部分来展示最新的数据。本文将介绍如何使用 Vue 3 和 Pinia 来实现这一功能。 技术栈 Vue 3…...
![](https://img-blog.csdnimg.cn/img_convert/d262c105b950158f2e92faf36da52a2d.png)
DVWA综合靶场漏洞讲解
目录 综合靶场漏洞讲解 Brute Force Low Medium High Command Injection Low Medium High File Inclusion Low,Medium,High File Upload Low Medium High SQL Injection Low Medium High SQL Injection (Blind) Low Medium High XSS(DOM&am…...
![](https://i-blog.csdnimg.cn/direct/ebfd03f4e34347acb813eb1b90ebc499.png)
实现Bezier样条曲线
1.给出n1 个控制点pk(xk,yk,zk),这里k可取值0-n,多项式函数公式如下 获取的单个点的代码 void zmBezier::getPoint(float u, double p[3]) {int n m_count - 1;double x 0, y 0, z 0;for(int k 0; k < n; k){x m_ctrlPoints[k][0] * BEZ_k_n(n, k, u);y m_ctrlPoin…...
![](https://i-blog.csdnimg.cn/direct/adde2c0985bd490b98df0252d5193e99.png)
MySQL中的EXPLAIN的详解
一、介绍 官网介绍: https://dev.mysql.com/doc/refman/5.7/en/explain-output.htmlhttps://dev.mysql.com/doc/refman/8.0/en/explain-output.htmlexplain(执行计划),使用explain关键字可以模拟优化器执行sql查询语句ÿ…...
![](https://i-blog.csdnimg.cn/direct/b67e47773e9d49f0bc7b8880fb6d7d5b.png)
LearnOpenGL——SSAO学习笔记
LearnOpenGL——SSAO学习笔记 SSAO一、基本概念二、样本缓冲三、法向半球四、随机核心转动五、SSAO着色器六、环境遮蔽模糊七、应用SSAO遮蔽因子 SSAO 一、基本概念 环境光照是我们加入场景总体光照中的一个固定光照常量,它被用来模拟光的散射(Scattering)。散射应…...
![](https://i-blog.csdnimg.cn/direct/fc4697f8861348ac8820fbf6d9cb10b0.png)
[C语言]-基础知识点梳理-文件管理
前言 各位师傅们好,我是qmx_07,今天给大家讲解文件管理的相关知识,也就是常见的 读取,删除一类的操作 文件 为什么要使用文件? 程序的数据是存储在电脑的内存中,如果程序退出,内存回收&…...
![](https://img-blog.csdnimg.cn/img_convert/5c2a043a555365e66f866b256645ec99.jpeg)
pcdn闲置带宽被动收入必看教程。第五讲:光猫更换和基础设置
PCDN闲置带宽被动收入必看教程 —— 第五讲:光猫更换和基础设置 为了从闲置带宽中获得被动收入,高效的网络设备至关重要。运营商提供的光猫通常能满足日常家用需求,但对于PCDN应用来说,它们可能不足以提供所需的高性能和稳定性。…...
![](https://img-blog.csdnimg.cn/img_convert/6b6093a5d8f8cab4144c01dc2f5dddf8.jpeg)
工业数据采集网关简介-天拓四方
随着工业4.0和物联网(IoT)技术的深入发展,工业数据采集网关作为连接现场设备与上层管理系统的关键节点,其在智能工厂中的作用愈发凸显。本文将深入探讨工业数据采集网关的功能、特点、应用场景及其实操性,以期为读者提…...
![](https://i-blog.csdnimg.cn/direct/d3ce2ee597fe4aff8e2cf0ea6cfc99bd.png)
Java 调整字符串,验证码生成
package text7;public class ZiFanz {public static void main(String[] args) {//1.定义两个字符串String strA "abcde";String strB "deabc";//2.abcde->bcdea->cdeab->deabc旋转字符串//旋转并比较boolean result cheak(strA, strB);System…...
![](https://i-blog.csdnimg.cn/direct/b5c7a4f13e4642a8822f93d7066c0c36.png)
【专题】全球商用服务机器人市场研究(2023)报告合集PDF分享(附原数据表)
原文链接:https://tecdat.cn/?p37366 近年来,随着人工智能、物联网和自动化技术的不断进步,商用服务机器人行业迅速崛起,展现出广阔的发展前景。从最初的实验室研发到如今的规模化应用,商用服务机器人已逐渐成为各行…...
![](https://i-blog.csdnimg.cn/direct/fdabec0c6c514d4aa64d6025032e3065.png)
SQL UA注入 (injection 第十八关)
简介 SQL注入(SQL Injection)是一种常见的网络攻击方式,通过向SQL查询中插入恶意的SQL代码,攻击者可以操控数据库,SQL注入是一种代码注入攻击,其中攻击者将恶意的SQL代码插入到应用程序的输入字段中&a…...
![](https://i-blog.csdnimg.cn/direct/06061195f5034a27a72e2d4309a0ebeb.png)
初阶数据结构之计数排序
非比较排序 计数排序 计数排序⼜称为鸽巢原理,是对哈希直接定址法的变形应⽤。 操作步骤: 1)统计相同元素出现次数 2)根据统计的结果将序列回收到原来的序列中 #include "CountSort.h" void Count(int* arr, int n)…...
![](https://i-blog.csdnimg.cn/direct/009c8d2cbed34971a2041dc72b5fffda.png)
【开端】记一次诡异的接口排查过程
一、绪论 最近碰到这么一个情况,接口请求超时。前提是两台服务器间的网络是畅通的,端口也是通,应用代码也是通。意思是在应用上,接口没有任何报错,能正常返回数据。客户端到服务端接口也能通,但是接收不到服…...
![](https://i-blog.csdnimg.cn/direct/488c3f8095b243288378632a4c889580.png)
jenkins最佳实践(二):Pipeline流水线部署springCloud微服务项目
各位小伙伴们大家好呀,我是小金,本篇文章我们将介绍如何使用Pipeline流水线部署我们自己的微服务项目,之前没怎么搞过部署相关的,以至于构建流水线的过程中中也遇到了很多自己以前没有考虑过的问题,特写此篇࿰…...
![](https://i-blog.csdnimg.cn/direct/433615690d4e43978b41541e771843ab.png#pic_center)
第2章 C语言基础知识
第2章 C语言基础知识 1.printf()函数 在控制台输出数据,需要使用输出函数,C语言常用的输出函数为printf()。 printf()函数为格式化输出函数,其功能是按照用户指定的格式将数据输出到屏幕上。 printf(“格式控制字符串”,[输出列表]); 格式控…...
![](https://i-blog.csdnimg.cn/direct/aa8cfe909b314e2cb1b966869d30bfb2.jpeg#pic_center)
鹭鹰优化算法SBOA优化RBF神经网络的扩散速度实现多数入多输出数据预测,可以更改数据集(MATLAB代码)
一、鹭鹰优化算法介绍 鹭鹰优化算法(Secretary Bird Optimization Algorithm, SBOA)是一种新型的元启发式算法,它于2024年4月由Youfa Fu等人提出,并发表在SCI人工智能二区顶刊《Artificial Intelligence Review》上。该算法的灵感…...
![](https://i-blog.csdnimg.cn/direct/61befcba12bd45b58dc2c08e433751fe.png)
MySQL基础练习题48-连续出现的数字
目录 题目 准备数据 分析数据 题目 找出所有至少连续出现三次的数字。 准备数据 ## 创建库 create database db; use db;## 创建表 Create table If Not Exists Logs (id int, num int)## 向表中插入数据 Truncate table Logs insert into Logs (id, num) values (1, 1) i…...
![](https://i-blog.csdnimg.cn/direct/9418d289348447f6a4532fd6c74bc6d8.png)
webrtc学习笔记2
音视频采集和播放 打开摄像头并将画面显示到页面 1. 初始化button、video控件 2. 绑定“打开摄像头”响应事件onOpenCamera 3. 如果要打开摄像头则点击 “打开摄像头”按钮,以触发onOpenCamera事件的调用 4. 当触发onOpenCamera调用时 a. 设置约束条件,…...
![](https://i-blog.csdnimg.cn/direct/78488bc8a8d64c3e8c2b14f3ec6cec17.jpeg#pic_center)
Simple RPC - 06 从零开始设计一个服务端(上)_注册中心的实现
文章目录 Pre核心内容服务端结构概述注册中心的实现1. 注册中心的架构2. 面向接口编程的设计3. 注册中心的接口设计4. SPI机制的应用 小结 Pre Simple RPC - 01 框架原理及总体架构初探 Simple RPC - 02 通用高性能序列化和反序列化设计与实现 Simple RPC - 03 借助Netty实现…...
![](/images/no-images.jpg)
太原网站建设培训学校/线上运营推广
网页设计中计算机图像处理技术的运用(共2642字)网页设计中计算机图像处理技术的运用(共2642字)随着计算机技术的不断发展,社会对网页设计的质量要求越来越高,如何将网页内容所要表达的内容形象的展现在浏览者面前是计算机图像处理技术的重要任务。在网页…...
![](https://www.oschina.net/img/hot3.png)
dedecms制作网站地图/厦门百度关键词推广
2019独角兽企业重金招聘Python工程师标准>>> 1.运行时常量池属于线程共享区中的方法区。 2.运行时常量池用于编译期生成的各种自变量,符号引用,这部分内用将在类加载后接入方法区的运行时常量池中存放。 看如下代码所示,如图&…...
长春疫情最新消息今天封城了/外贸谷歌优化
之前客户给了一个域名检测api接口,专门对接程序的,用于程序自动检测域名轮转,拦截域名自动下线用的最近买域名比较频繁,总是入坑,一不小心买回来的新域名就是已经被微信拦截的,估计是域名上一任主人没有善待…...
![](https://img-blog.csdnimg.cn/img_convert/a36003175252b610fc39a2a0e1a809d1.png)
如何用api做网站/百度网页版入口链接
说到地图面积大的游戏,《无人深空》肯定榜上有名。不过这款游戏有几分投机取巧的意味,它是一款以太空探索为主题的冒险生存游戏。它拥有1844亿亿颗星球,就算玩家一秒钟就逛完了一颗星球那只要也要5850亿年的时间。只不过它的游戏地图是随机生…...
![](/images/no-images.jpg)
网站建设自主开发的三种方式/北京seo薪资
转载:http://softwareblogs-zho.intel.com/2009/02/04/1071/ 程序员的十层楼(1~3层)作者: 周伟明 自西方文艺复兴以来,中国在自然科学方面落后西方很多,软件领域也不例外。当然现在中国的许多程序员们对此可能有许多不同的意见&am…...
![](/images/no-images.jpg)
个人制作一个网站的费用/交换友情链接的方法
http://www.cnblogs.com/hh54188/archive/2011/04/09/1996469.html 动画队列解释 animate 必需的 params 参数定义形成动画的 CSS 属性。 可选的 speed 参数规定效果的时长。它可以取以下值:"slow"、"fast" 或毫秒。 可选的 callback 参数是动…...