0


Threejs 中使用HTML融合、交互CSS2DObject对象(六)

文章目录

一、参考

  1. threejs 基础教程, 郭隆邦
  2. threejs API 文档和案例
  3. CSS2DRenderer(HTML标签)

二、学习遇到的问题

在学习官方案例 css2d — label 发现直接给 div dom添加事件一直不生效,经过努力排查,有两个阶段:

  1. 分析到的原因是受到了 OrbitControls 控件的影响,删掉了就可以正常使用了 (错误结论
  2. OrbitControls 控制的DOM 节点对象不对,应该要控制 WebGLRenderer 对象

在这里插入图片描述

OrbitControls api 介绍

OrbitControls API

在这里插入图片描述

三、案例说明

<template><divclass="box-card-left"><divid="threejs"style="border: 1px solid red;position: relative;"></div></div></template><script>import*asTHREEfrom'three';// 引入轨道控制器扩展库OrbitControls.jsimport{ OrbitControls }from"three/examples/jsm/controls/OrbitControls.js";import{ CSS2DObject, CSS2DRenderer }from"three/examples/jsm/renderers/CSS2DRenderer.js";exportdefault{data(){return{// name: "",// scene: null,// camera: null,// renderer: null,// css2DRenderer: null,// effectComposer: null,// mesh: null,// geometry: null,// group: null,// material: null,// texture: null,// position: null,// outlinePass: null,canvasWidth:1000,canvasHeight:800,// color: [],// meshArr: [],};},created(){},mounted(){this.name =this.$route.query.name;this.init();},methods:{goBack(){this.$router.go(-1);},init(){//  创建场景对象this.scene =newTHREE.Scene();// 调用方法创建盒模型 1this.careateBox1();// 调用方法创建盒模型 2this.careateBox2();this.createCone();// 创建圆锥// 创建辅助坐标轴对象const axesHelper =newTHREE.AxesHelper(200);this.scene.add(axesHelper);// 创建透视投影相机对象this.camera =newTHREE.PerspectiveCamera(60,1,0.01,2000);// 设置相机位置this.camera.position.set(200,200,200);// 设置相机指向this.camera.lookAt(0,0,0);// 创建WebGL渲染器对象this.renderer =newTHREE.WebGLRenderer();// 设置渲染器渲染的尺寸this.renderer.setSize(this.canvasWidth,this.canvasHeight);// 调用渲染器的render方法开始渲染,参数是 场景,相机 对象this.renderer.render(this.scene,this.camera);// 获取id 是 threejs 的元素并添加domElement
      window.document.getElementById("threejs").appendChild(this.renderer.domElement);this.renderEventBinding()// 创建 CSS2D 渲染器对象(主要:)this.css2DRenderer =newCSS2DRenderer();this.css2DRenderer.setSize(1000,800);this.css2DRenderer.render(this.scene,this.camera);this.css2DRenderer.domElement.style.position ="absolute";this.css2DRenderer.domElement.style.top =0;this.css2DRenderer.domElement.style.pointerEvents ='none';// 取消标签的点击事件
      window.document.getElementById("threejs").appendChild(this.css2DRenderer.domElement);this.renderFun();// 创建空间轨道控制器对象const controls =newOrbitControls(this.camera,this.renderer.domElement);
      controls.addEventListener("change",()=>{this.renderer.render(this.scene,this.camera);})},// 绑定点击事件renderEventBinding(){this.renderer.domElement.addEventListener('click',(event)=>{// .offsetY、.offsetX以canvas画布左上角为坐标原点,单位pxconst px = event.offsetX;const py = event.offsetY;//屏幕坐标px、py转WebGL标准设备坐标x、y//width、height表示canvas画布宽高度const x =(px /this.canvasWidth)*2-1;const y =-(py /this.canvasHeight)*2+1;//创建一个射线投射器`Raycaster`const raycaster =newTHREE.Raycaster();//.setFromCamera()计算射线投射器`Raycaster`的射线属性.ray// 形象点说就是在点击位置创建一条射线,用来选中拾取模型对象
        raycaster.setFromCamera(newTHREE.Vector2(x, y),this.camera);//.intersectObjects([mesh1, mesh2, mesh3])对参数中的网格模型对象进行射线交叉计算// 未选中对象返回空数组[],选中一个对象,数组1个元素,选中多个对象,数组多个元素const intersects = raycaster.intersectObjects([this.mesh,this.cone_mesh]);
        console.log("射线器返回的对象", intersects);// intersects.length大于0说明,说明选中了模型if(intersects.length >0){// console.log("交叉点", intersects[0].point);// console.log("交叉对象",intersects[0].object)// 选中模型的第一个模型,设置为红色
          intersects[0].object.material.color.set(0xff0000);}})},// 创建一个盒模型,父组件是 scene 场景;盒模型设置了位置careateBox1(){// 创建立方缓存几何体对象this.geometry =newTHREE.BoxGeometry(50,50,60);// 创建网格基础材质对象this.material =newTHREE.MeshBasicMaterial({color:0xfafa,});// 创建网格对象this.mesh =newTHREE.Mesh(this.geometry,this.material);// 设置网格对象位置this.mesh.position.set(-50,60,60);// 调用方法创建html元素let dom =this.createDiv("父对象是Scene的模型的标签");const tag =newCSS2DObject(dom);// 创建Vector3对象const wp =newTHREE.Vector3();// 获得this.mesh的世界坐标this.mesh.getWorldPosition(wp);
      tag.position.set(wp.x, wp.y, wp.z);// this.mesh.add(tag);this.scene.add(this.mesh);this.scene.add(tag);},// 创建一个盒模型,父组件是 组对象,盒模型与组对象都设置了位置careateBox2(){const group =newTHREE.Group();
      group.position.set(30,0,0);const geometry =newTHREE.BoxGeometry(50,50,60);const material =newTHREE.MeshBasicMaterial({color:0xfaf33a,});const mesh =newTHREE.Mesh(geometry, material);// 网格模型设置了位置,且父对象是组对象,这样css2模型对象添加到mesh中后,会直接继承mesh的世界坐标
      mesh.position.set(50,60,-60);let dom =this.createDiv("父对象是Group的模型标签");const obj =newCSS2DObject(dom);
      mesh.add(obj);
      group.add(mesh);this.scene.add(group);},// 圆锥createCone(){// 创建圆锥缓冲几何体对象const coneGeometry =newTHREE.ConeGeometry(20,100,32);// 创建材质对象const material =newTHREE.MeshBasicMaterial({color:0xffaadd});// 创建网格模型对象const cone_mesh =newTHREE.Mesh(coneGeometry, material);const pos = coneGeometry.attributes.position;const dom =this.createDiv("我是圆锥的标注标签");const tag =newCSS2DObject(dom);
      cone_mesh.add(tag);
      cone_mesh.translateY(50);// 圆锥几何体Y轴平移50
      tag.position.y +=50;//  css2模型对象this.cone_mesh = cone_mesh // 用来允许用户点击this.scene.add(cone_mesh);},createDiv(innerText='盒子模型标签'){let dom = window.document.createElement("div");
      dom.innerHTML = innerText;
      dom.style.border ='1px solid blue';
      dom.style.height ='20px';
      dom.style.color ='#0A58F6';
      dom.style.padding ='5px 10px';
      dom.style.borderRadius ="5px";
      dom.style.top ='-20px';
      dom.style.background ="#F7F2EE";
      dom.style.pointerEvents ='auto'// 重点,因为css2d渲染器我们设置了pointerEvents = none

      dom.addEventListener('click',function(event){
        console.log('click', event.currentTarget)// 注意event的坐标,因为css2dobject是绝对定位 + transform})return dom;},renderFun(){this.renderer.render(this.scene,this.camera);this.css2DRenderer.render(this.scene,this.camera);
      window.requestAnimationFrame(this.renderFun);},},};</script>
//
<stylelang="scss"scoped>.box-card-left{display: flex;align-items: flex-start;flex-direction: row;position: relative;width: 100%;.box-right{img{width:500px;user-select: none;}}}</style>

在这里插入图片描述

四、官方案例 (月亮围绕地球转)

<!DOCTYPEhtml><html><head><metacharset="utf-8"><metaname="viewport"content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"><title>three.js css2d - label</title><linktype="text/css"rel="stylesheet"href="main.css"><style>.label{color: #FFF;font-family: sans-serif;padding: 2px;background:rgba(0, 0, 0, .6);}</style></head><body><divid="info"><ahref="https://threejs.org"target="_blank"rel="noopener">three.js</a> css2d - label</div><scripttype="importmap">{"imports":{"three":"../build/three.module.js","three/addons/":"./jsm/"}}</script><scripttype="module">import*asTHREEfrom'three';import{ OrbitControls }from'three/addons/controls/OrbitControls.js';import{ CSS2DRenderer, CSS2DObject }from'three/addons/renderers/CSS2DRenderer.js';import{GUI}from'three/addons/libs/lil-gui.module.min.js';let gui;let camera, scene, renderer, labelRenderer;const layers ={'Toggle Name':function(){

            camera.layers.toggle(0);},'Toggle Mass':function(){

            camera.layers.toggle(1);},'Enable All':function(){

            camera.layers.enableAll();},'Disable All':function(){

            camera.layers.disableAll();}};const clock =newTHREE.Clock();const textureLoader =newTHREE.TextureLoader();let moon;init();animate();functioninit(){constEARTH_RADIUS=1;constMOON_RADIUS=0.27;

        camera =newTHREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight,0.1,200);
        camera.position.set(10,5,20);
        camera.layers.enableAll();

        scene =newTHREE.Scene();const dirLight =newTHREE.DirectionalLight(0xffffff,3);
        dirLight.position.set(0,0,1);
        dirLight.layers.enableAll();
        scene.add( dirLight );const axesHelper =newTHREE.AxesHelper(5);
        axesHelper.layers.enableAll();
        scene.add( axesHelper );//const earthGeometry =newTHREE.SphereGeometry(EARTH_RADIUS,16,16);const earthMaterial =newTHREE.MeshPhongMaterial({specular:0x333333,shininess:5,map: textureLoader.load('textures/planets/earth_atmos_2048.jpg'),specularMap: textureLoader.load('textures/planets/earth_specular_2048.jpg'),normalMap: textureLoader.load('textures/planets/earth_normal_2048.jpg'),normalScale:newTHREE.Vector2(0.85,0.85)});
        earthMaterial.map.colorSpace =THREE.SRGBColorSpace;const earth =newTHREE.Mesh( earthGeometry, earthMaterial );
        scene.add( earth );const moonGeometry =newTHREE.SphereGeometry(MOON_RADIUS,16,16);const moonMaterial =newTHREE.MeshPhongMaterial({shininess:5,map: textureLoader.load('textures/planets/moon_1024.jpg')});
        moonMaterial.map.colorSpace =THREE.SRGBColorSpace;
        moon =newTHREE.Mesh( moonGeometry, moonMaterial );
        scene.add( moon );//

        earth.layers.enableAll();
        moon.layers.enableAll();const earthDiv = document.createElement('div');
        earthDiv.className ='label';
        earthDiv.textContent ='Earth';
        earthDiv.style.backgroundColor ='transparent';const earthLabel =newCSS2DObject( earthDiv );
        earthLabel.position.set(1.5*EARTH_RADIUS,0,0);
        earthLabel.center.set(0,1);
        earth.add( earthLabel );
        earthLabel.layers.set(0);const earthMassDiv = document.createElement('div');
        earthMassDiv.className ='label';
        earthMassDiv.textContent ='5.97237e24 kg';// earthMassDiv.style.backgroundColor = 'transparent';
        earthMassDiv.style.backgroundColor ='red';/*
        由于父组件即 labelRenderer.domElement的 pointerEvents 样式设置为 'none',导致子DOM节点选中不到
        解决办法: 将子DOM 节点的样式pointerEvents 设置为 'all' 或者 'auto'
        * */
        earthMassDiv.style.pointerEvents ='all';// 取消标签的点击事件
        earthMassDiv.addEventListener('click',function(e){
            console.log('我被点击了', e, e.srcElement );});const earthMassLabel =newCSS2DObject( earthMassDiv );
        earthMassLabel.position.set(1.5*EARTH_RADIUS,0,0);
        earthMassLabel.center.set(0,0);
        earth.add( earthMassLabel );
        earthMassLabel.layers.set(1);const moonDiv = document.createElement('div');
        moonDiv.className ='label';
        moonDiv.textContent ='Moon';
        moonDiv.style.backgroundColor ='transparent';const moonLabel =newCSS2DObject( moonDiv );
        moonLabel.position.set(1.5*MOON_RADIUS,0,0);
        moonLabel.center.set(0,1);
        moon.add( moonLabel );
        moonLabel.layers.set(0);const moonMassDiv = document.createElement('div');
        moonMassDiv.className ='label';
        moonMassDiv.textContent ='7.342e22 kg';
        moonMassDiv.style.backgroundColor ='transparent';const moonMassLabel =newCSS2DObject( moonMassDiv );
        moonMassLabel.position.set(1.5*MOON_RADIUS,0,0);
        moonMassLabel.center.set(0,0);
        moon.add( moonMassLabel );
        moonMassLabel.layers.set(1);//

        renderer =newTHREE.WebGLRenderer();
        renderer.setPixelRatio( window.devicePixelRatio );
        renderer.setSize( window.innerWidth, window.innerHeight );
        document.body.appendChild( renderer.domElement );

        labelRenderer =newCSS2DRenderer();
        labelRenderer.setSize( window.innerWidth, window.innerHeight );
        labelRenderer.domElement.style.position ='absolute';
        labelRenderer.domElement.style.top ='0px';/*
        * 由于 labelRenderer.domElement 是在renderer.domElement 的下面,会遮盖到canvas上,导致 鼠标无法控制3D 场景的旋转
        * 解决办法:将labelRenderer.domElement的 pointerEvents 样式设置为 'none'
        * */
        labelRenderer.domElement.style.pointerEvents ='none';// 取消标签的点击事件
        document.body.appendChild( labelRenderer.domElement );/*
        OrbitControls( object : Camera, domElement : HTMLDOMElement )
        object: (必须)将要被控制的相机。该相机不允许是其他任何对象的子级,除非该对象是场景自身。
        domElement: 用于事件监听的HTML元素。
        * */// 摄像头 影响的是 canvasconst controls =newOrbitControls( camera, renderer.domElement );
        controls.minDistance =5;
        controls.maxDistance =100;

        window.addEventListener('resize', onWindowResize );initGui();}functiononWindowResize(){

        camera.aspect = window.innerWidth / window.innerHeight;

        camera.updateProjectionMatrix();

        renderer.setSize( window.innerWidth, window.innerHeight );

        labelRenderer.setSize( window.innerWidth, window.innerHeight );}functionanimate(){requestAnimationFrame( animate );const elapsed = clock.getElapsedTime();

        moon.position.set( Math.sin( elapsed )*5,0, Math.cos( elapsed )*5);

        renderer.render( scene, camera );
        labelRenderer.render( scene, camera );}//functioninitGui(){

        gui =newGUI();

        gui.title('Camera Layers');

        gui.add( layers,'Toggle Name');
        gui.add( layers,'Toggle Mass');
        gui.add( layers,'Enable All');
        gui.add( layers,'Disable All');

        gui.open();}</script></body></html>

关键点:

  1. new OrbitControls( camera, renderer.domElement ); 是监听 WebGLRenderer 的dom事件,即允许鼠标控制3D场景
  2. CSS2DRenderer对象的dom要设置为 pointerEvents 样式设置为 ‘none’,这样就不会影响鼠标控制WebGLRenderer的渲染场景
  3. 要将HTML对象监听事件的DOM节点样式pointerEvents 设置为 ‘all’ 或者 ‘auto’,这样是保证他不受到父组件pointerEvents 样式设置为 'none’的影响

在这里插入图片描述

标签: html 交互 前端

本文转载自: https://blog.csdn.net/hbiao68/article/details/140319241
版权归原作者 胖鹅68 所有, 如有侵权,请联系我们删除。

“Threejs 中使用HTML融合、交互CSS2DObject对象(六)”的评论:

还没有评论