如何从Three.js场景中移除CSS2DObject组成的Group
问题解决:Three.js CSS2DObject 旧Group无法完全移除的处理方案
不需要重新创建CSS2DRenderer,问题核心在于单纯从场景移除Group不会自动清理CSS2DObject对应的DOM元素,加上初始逻辑存在小瑕疵。以下是具体解决步骤和优化代码:
核心问题分析
- 首次调用
errsIface时,this.errs未初始化,执行this.scene.remove(this.errs)实际是移除undefined,无影响;但后续移除时,Three.js仅从场景图中移除Group,不会自动删除CSS2DObject绑定的DOM节点——这些节点仍会留在CSS2DRenderer的容器中,导致视觉上旧元素未消失。 - 每次创建新Group的方式虽可行,但不如复用现有Group并清空内容高效。
解决方案
1. 完善旧Group的移除与DOM清理逻辑
移除旧Group前,先遍历其所有子元素,手动删除对应的DOM节点,再从场景中移除Group(或直接清空Group内容)。
2. 优化ErrVisu类实现
修改errsIface方法,确保:
- 先检查旧Group是否存在,存在则清理子元素的DOM并清空Group
- 复用现有Group而非每次创建新实例,提升性能
修改后的代码示例
var camera, scene, renderer, errs; /** ErrVisu class */ class ErrVisu{ constructor(scene){ this.scene = scene; // 提前初始化errs Group,避免重复创建实例 this.errs = new THREE.Group(); this.errs.name = "errors"; this.scene.add(this.errs); } visuError=(x, y)=>{ const x_mm = y * 0.8 - 8.8; const y_mm = 2.5; const z_mm = x * 0.8 - 2.4; const circle = document.createElement('div'); circle.className = "circle"; // 改用class避免id重复 circle.innerHTML = "!"; const objectCSS = new THREE.CSS2DObject(circle); objectCSS.position.set(x_mm, y_mm, z_mm); objectCSS.name = 'exc_' + x + y; this.errs.add(objectCSS); } errsIface = (msg_arr) => { // 清空旧错误元素:先删除DOM节点,再清空Group while(this.errs.children.length > 0) { const child = this.errs.children[0]; // 删除CSS2DObject对应的DOM节点 if(child.element && child.element.parentNode) { child.element.parentNode.removeChild(child.element); } // 从Group中移除子元素 this.errs.remove(child); } // 添加新错误元素 for (let k in msg_arr) { let p_xx_yy = msg_arr[k].split("_"); let x = Number(p_xx_yy[1]); let y = Number(p_xx_yy[2]); this.visuError(x, y); } } } /** Create the scene, camera, renderer */ function init() { scene = new THREE.Scene(); scene.background = new THREE.Color(0x21252d); renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000); camera.position.x = 1; camera.position.y = 4; camera.position.z = 5; scene.add(camera); labelRenderer = new THREE.CSS2DRenderer(); labelRenderer.setSize( window.innerWidth, window.innerHeight ); labelRenderer.domElement.style.position = 'absolute'; labelRenderer.domElement.style.top = '0px'; document.body.appendChild( labelRenderer.domElement ); controls = new THREE.OrbitControls(camera, labelRenderer.domElement); errs = new ErrVisu(scene); let msg_arr_ = [ "p_06_08" ]; errs.errsIface(msg_arr_); window.addEventListener('resize', onWindowResize); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); labelRenderer.setSize( window.innerWidth, window.innerHeight ); } function animate() { requestAnimationFrame(animate); render(); } function render() { renderer.render(scene, camera); labelRenderer.render( scene, camera ); } init(); animate();
对应的CSS修改(将id改为class,避免重复id冲突):
.circle { color: #ffffff; background: #ff5500; width: 30px; height: 30px; border-radius: 50%; text-align: center; font-size: 25px; font-weight: bold; display: inline-block; animation: blinkingBackground 0.5s infinite; } @keyframes blinkingBackground { 0% { opacity: 1; } 25% { opacity: 0.5; } 50% { opacity: 0.1; } 75% { opacity: 0.5; } 100% { opacity: 1; } }
关键优化点说明
- DOM节点手动清理:遍历Group子元素,删除
child.element(CSS2DObject对应的DOM节点),确保旧元素完全从页面消失。 - 复用Group实例:在constructor中初始化Group,后续仅清空内容而非创建新实例,减少内存开销。
- 避免重复id:将
circle的id改为class,防止多个错误元素的id冲突导致样式异常。 - 无需重建CSS2DRenderer:CSS2DRenderer是全局渲染容器,单个实例即可管理所有CSS2DObject,重建会导致DOM容器重复,反而引发问题。
内容的提问来源于stack exchange,提问作者Bilal
相关产品推荐
相关产品推荐

