1. CSS3DObject 点击事件被 OrbitControls 拦截的解决方案

349 阅读2分钟

如下面,有两个渲染器:

  • CSS3DRenderer​:用于渲染CSS3D元素
  • WebGLRenderer​:用于渲染主要场景
import * as THREE from 'three'
import { loadGLTFModel, loadHDRTexture } from './helpers/loaders';
import { CSS3DObject, CSS3DRenderer, OrbitControls, } from 'three/examples/jsm/Addons.js';
import { MouseEventManager } from './events/mouseEventManager';
import { createLines } from './init/lines';
import { useConnectStore } from '@/store/collect';
import touchScreenComponent from '@/components/touchScreen.vue';
import { createApp } from 'vue';

export class SceneManager {
    container: HTMLElement; // 容器
    scene: THREE.Scene; // 场景
    camera: THREE.PerspectiveCamera; // 相机
    renderer: THREE.WebGLRenderer; // 渲染器
    control: OrbitControls; // 相机控制
    loadingManager: THREE.LoadingManager; // 加载管理器
    mouseEventManager: MouseEventManager | null = null; // 鼠标事件管理器
    css3DRenderer: CSS3DRenderer; // CSS3D渲染器

    constructor(container: HTMLElement, state: { loading: boolean; progress: number }) {
		// ......

        this.renderer = new THREE.WebGLRenderer({
            antialias: true, // 抗锯齿
        });
        this.css3DRenderer = new CSS3DRenderer();
        this.control = new OrbitControls(this.camera, this.css3DRenderer.domElement);

        // 初始化加载管理器
		// ......
    }

    async init() {
        this.renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染器尺寸
        this.container.appendChild(this.renderer.domElement); // 将渲染器添加到容器中

        this.css3DRenderer.setSize(window.innerWidth, window.innerHeight); // 设置 CSS3D 渲染器尺寸
        this.css3DRenderer.domElement.style.position = 'absolute'; // 设置 CSS3D 渲染器的定位方式为绝对定位
        this.css3DRenderer.domElement.style.top = '0'; // 设置 CSS3D 渲染器的顶部为 0
        this.container.appendChild(this.css3DRenderer.domElement); // 将 CSS3D 渲染器添加到容器中

        // 设置相机位置
        this.camera.position.set(0, 0, 5);

        try {
            // 加载模型
            const model = await loadGLTFModel('/models/test.glb', this.loadingManager);
            model && this.scene.add(model.scene);
            // 获取名称为 touchScreen 的模型
            const touchScreen = model.scene.getObjectByName('touchScreen') as THREE.Mesh;
            const div = document.createElement('div');
            document.body.appendChild(div);
            const app = createApp(touchScreenComponent);
            app.mount(div);
        
            // 将 div 转换为 CSS3DObject 对象
            const css3DObject = new CSS3DObject(div);
            // 设置 CSS3DObject 对象的位置
            css3DObject.position.set(0, 0, 0.03);
            css3DObject.scale.set(0.01, 0.01, 0.01);
            css3DObject.visible = true;
            // 将 CSS3DObject 对象添加到模型中
            touchScreen.add(css3DObject);

			// ......

        } catch (error) {
            console.error(error);
        }

        this.animate();
    }


    animate() {
        this.renderer.render(this.scene, this.camera);
        this.css3DRenderer.render(this.scene, this.camera);
        requestAnimationFrame(this.animate.bind(this));
    }

    dispose() {
        this.renderer.dispose();
        this.control.dispose();
    }
}
<template>
    <div class="w-[65px] h-[45px] bg-black text-white flex justify-center items-center text-[5px]">
        <button class="w-[20px] h-[15px] bg-red-500" @click="test">点击</button>
    </div>
</template>

<script setup lang="ts">
const test = () => {
    console.log('test')
}
</script>

<style scoped></style>

5ad978f94e3aa3c9518954d27e45e35c.png

问题描述:

通过 CSS3DObject​ 将 TouchScreen.vue​ 转换成 3D模型 并挂载到场景上后,TouchScreen.vue​ 的点击事件失效了

问题解析和解决:

有两个原因:

  1. CSS3DRenderer​ 的层级比 WebGLRenderer​ 高,也就是说点击事件都被 CSS3DRenderer​ 捕获了

    this.renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染器尺寸
    this.container.appendChild(this.renderer.domElement); // 将渲染器添加到容器中
    
    this.css3DRenderer.setSize(window.innerWidth, window.innerHeight); // 设置 CSS3D 渲染器尺寸
    this.css3DRenderer.domElement.style.position = 'absolute'; // 设置 CSS3D 渲染器的定位方式为绝对定位
    this.css3DRenderer.domElement.style.top = '0'; // 设置 CSS3D 渲染器的顶部为 0
    this.container.appendChild(this.css3DRenderer.domElement); // 将 CSS3D 渲染器添加到容器中
    

    所以我们需要通过 pointer-events:none​ 设置 CSS3D 渲染器的鼠标事件为不可用

    this.renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染器尺寸
    this.container.appendChild(this.renderer.domElement); // 将渲染器添加到容器中
    
    this.css3DRenderer.setSize(window.innerWidth, window.innerHeight); // 设置 CSS3D 渲染器尺寸
    this.css3DRenderer.domElement.style.position = 'absolute'; // 设置 CSS3D 渲染器的定位方式为绝对定位
    this.css3DRenderer.domElement.style.top = '0'; // 设置 CSS3D 渲染器的顶部为 0
    this.css3DRenderer.domElement.style.pointerEvents = 'none'; // 设置 CSS3D 渲染器的鼠标事件为不可用
    this.container.appendChild(this.css3DRenderer.domElement); // 将 CSS3D 渲染器添加到容器中
    
  2. OrbitControls​ 的问题:

    其实和上面同源问题,CSS3DObject​ 是叠加在 WebGL​ 渲染的场景之上的,OrbitControls 默认会拦截所有鼠标事件

    当前 OrbitControls 绑定在了 CSS3DRenderer 的 domElement 上,导致点击事件被 OrbitControls 消费掉了

    this.control = new OrbitControls(this.camera, this.css3DRenderer.domElement);
    

    需要改成

    this.control = new OrbitControls(this.camera, this.renderer.domElement);