使用CSS3DRenderer/CSS2DRenderer给模型上面添加html标签

先放一下预览图

主要使用css2dRender和css3dRender,添加图片和标签。

思路:使用css3dRender添加一个图片,然后获取的位置坐标,使用css3dRender添加一个文字标签,也设置这个位置坐标,此外z轴设置一个高度,这样就可以放在图片的正上方。图片添加一个点击事件,点击之后会出现一个弹窗。海岛介绍文本框,使用css2dRender。

下面的通过css3dRender创建的html标签,都是放到了模型上面,这个要注意,不是放在场景scene里面。

使用css3dRender添加图片

addimg(img, fun) {

const imgDiv = document.createElement('div');

imgDiv.style.width = '25px';

imgDiv.style.height = '25px';

imgDiv.style.backgroundImage = `url(${img})`;

imgDiv.style.backgroundSize = 'contain';

imgDiv.style.backgroundRepeat = 'no-repeat';

imgDiv.style.cursor = 'pointer'; // 鼠标悬停时显示为手型

imgDiv.style.zIndex = '1000'

imgDiv.addEventListener('click', fun)

const imgLabel = new CSS3DObject(imgDiv);

imgLabel.position.set(1, 0, 0);

return imgLabel

},

使用css3dRender添加文本

addtext(text) {

// 创建第二个文本标签

const labelDiv = document.createElement('div');

labelDiv.innerHTML = text;

labelDiv.style.color = 'white';

labelDiv.style.fontSize = '5px';

labelDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';

labelDiv.style.padding = '2px';

labelDiv.style.borderRadius = '3px';

const label = new CSS3DObject(labelDiv);

label.position.set(0, 1, 0); // 相对于模型的局部位置

return label

},

这里有一点需要注意一下,就是添加渲染器css3dRender,他会增加一个html标签,默认会叠加到父元素canvas上面,这样就会导致鼠标缩放,模型无法缩放,所以需要设置pointerEvents属性为none,解决html标签对画布的遮盖。对于具体添加的某个css3dObject对象,我们可以看需求,然后决定是否添加 div.style.pointerEvents = 'none'; 如果添加了,就是说对这个html标签设置任何点击事件,都无法起作用。

添加一个obj格式的模型,这里之前写过一篇文章,写了如何引入如何在three.js里面导入obj的三维模型_threejs 加载obj模型-CSDN博客

addmodel() {

// 加载三维模型

let obj;

const mtlLoader = new MTLLoader();

mtlLoader.load(blockmtl, (materials) => {

const objLoader = new OBJLoader();

objLoader.setMaterials(materials);

objLoader.load(block, (loadedObj) => {

obj = loadedObj;

obj.rotation.x = -20 * (Math.PI / 180);

obj.position.set(-2, 2, 0);

obj.scale.set(0.02, 0.02, 0.02);

// 创建文字标签

const labelDiv = document.createElement('div');

// labelDiv.className = 'label';

labelDiv.innerHTML = `海岛介绍</br>这是被海水围绕的小块陆地`;

labelDiv.style.color = 'white';

labelDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';

labelDiv.style.padding = '9px';

labelDiv.style.borderRadius = '5px';

const label = new CSS2DObject(labelDiv);

labelDiv.style.pointerEvents = 'none'

label.position.set(0, 0, 0); // 相对于模型的局部位置

obj.add(label);

// 创建图片标签

let imgLabel1 = this.addimg(img1, () => {

window.location.href = 'https://baidu.com';

})

obj.add(imgLabel1);

imgLabel1.position.y -= 70

imgLabel1.position.x += 90

let imgLabel2 = this.addimg(img2, () => {

this.showImageInfo('https://www.baidu.com')

})

obj.add(imgLabel2);

imgLabel2.position.y -= 120

imgLabel2.position.x += 140

// 创建文字标签

let label1 = this.addtext('电箱')

obj.add(label1);

label1.position.copy(imgLabel2.position); // 复制图片的位置

label1.position.y += 20; // 向上偏移

let label2 = this.addtext('路牌')

obj.add(label2);

label2.position.copy(imgLabel1.position); // 复制图片的位置

label2.position.y += 20; // 向上偏移

this.scene.add(obj);

});

});

},

复制代码
<template>
    <div class="box">
        <div id="wbglg" style="margin-top: 100px;">
        </div>
    </div>
</template>

<script>
import * as THREE from 'three';
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
import { CSS3DRenderer, CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import block from '../../public/obj/block/block.obj'
import blockmtl from '../../public/obj/block/block.mtl'
import img1 from '../assets/ganzi.png'
import img2 from '../assets/gaungai.png'

export default {
    name: "TestS",
    components: {},
    mounted() {
        this.init()
    },
    data() {
        return {
            controls: "",
            scene: '',
            renderer: '',
            camera: '',
            css2DRenderer: '',
            css3DRenderer: '',
        }
    },
    methods: {
        showImageInfo(detailUrl) {
            // 创建弹窗
            const popup = document.createElement('div');
            popup.style.position = 'fixed';
            popup.style.top = '60%';
            popup.style.left = '51%';
            popup.style.transform = 'translate(-50%, -50%)';
            popup.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'; // 半透明黑色背景
            popup.style.padding = '5px';
            popup.style.borderRadius = '10px'; // 圆角
            popup.style.boxShadow = '0 0 10px rgba(255, 255, 255, 0.2)'; // 白色阴影
            popup.style.zIndex = '1000';
            popup.style.fontSize = '14px';

            popup.style.color = 'white'; // 文字颜色
            popup.style.fontFamily = 'Arial, sans-serif'; // 字体
            popup.style.width = '190px'; // 弹窗宽度
            popup.style.textAlign = 'center'; // 文字居中

            // 创建关闭按钮(叉号)
            const closeButton = document.createElement('div');
            closeButton.innerHTML = '&times;'; // 叉号
            closeButton.style.position = 'absolute';
            closeButton.style.top = '10px';
            closeButton.style.right = '10px';
            closeButton.style.cursor = 'pointer'; // 鼠标悬停时显示为手型
            closeButton.style.fontSize = '24px'; // 叉号大小
            closeButton.style.color = 'white'; // 叉号颜色
            closeButton.style.userSelect = 'none'; // 禁止选中文本

            // 点击关闭按钮时移除弹窗
            closeButton.addEventListener('click', () => {
                document.body.removeChild(popup);
            });

            // 弹窗内容
            popup.innerHTML = `
        <h3>电箱信息</h3>
        <p>状态:正常</p>
        <p>管理人:张三</p>
        <p>联系电话:12323232323</p>
        <p style="color: #1E90FF; cursor: pointer;" onclick="window.open('${detailUrl}', '_blank')">点击查看详情</p>
    `;

            // 将关闭按钮添加到弹窗中
            popup.appendChild(closeButton);

            // 将弹窗添加到页面中
            document.body.appendChild(popup);
        },
        addmodel() {
            // 加载三维模型
            let obj;
            const mtlLoader = new MTLLoader();
            mtlLoader.load(blockmtl, (materials) => {
                const objLoader = new OBJLoader();
                objLoader.setMaterials(materials);
                objLoader.load(block, (loadedObj) => {
                    obj = loadedObj;
                    obj.rotation.x = -20 * (Math.PI / 180);
                    obj.position.set(-2, 2, 0);
                    obj.scale.set(0.02, 0.02, 0.02);

                    // 创建文字标签
                    const labelDiv = document.createElement('div');
                    // labelDiv.className = 'label';
                    labelDiv.innerHTML = `海岛介绍</br>这是被海水围绕的小块陆地`;
                    labelDiv.style.color = 'white';
                    labelDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
                    labelDiv.style.padding = '9px';
                    labelDiv.style.borderRadius = '5px';
                    const label = new CSS2DObject(labelDiv);
                    labelDiv.style.pointerEvents = 'none'
                    label.position.set(0, 0, 0); // 相对于模型的局部位置
                    obj.add(label);

                    // 创建图片标签
                    let imgLabel1 = this.addimg(img1, () => {
                        window.location.href = 'https://baidu.com';
                    })
                    obj.add(imgLabel1);
                    imgLabel1.position.y -= 70
                    imgLabel1.position.x += 90

                    let imgLabel2 = this.addimg(img2, () => {
                        this.showImageInfo('https://www.baidu.com')
                    })
                    obj.add(imgLabel2);
                    imgLabel2.position.y -= 120
                    imgLabel2.position.x += 140

                    // 创建文字标签
                    let label1 = this.addtext('电箱')
                    obj.add(label1);
                    label1.position.copy(imgLabel2.position); // 复制图片的位置
                    label1.position.y += 20; // 向上偏移

                    let label2 = this.addtext('路牌')
                    obj.add(label2);
                    label2.position.copy(imgLabel1.position); // 复制图片的位置
                    label2.position.y += 20; // 向上偏移
                    this.scene.add(obj);
                });
            });
        },
        animate() {
            requestAnimationFrame(this.animate);
            this.controls.update();
            this.renderer.render(this.scene, this.camera);
            this.css2DRenderer.render(this.scene, this.camera); // 更新 CSS2D 标签
            this.css3DRenderer.render(this.scene, this.camera); // 更新 CSS3D 标签
        },
        addtext(text) {
            const labelDiv = document.createElement('div');
            labelDiv.innerHTML = text;
            labelDiv.style.color = 'white';
            labelDiv.style.fontSize = '5px';
            labelDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
            labelDiv.style.padding = '2px';
            labelDiv.style.borderRadius = '3px';
            const label = new CSS3DObject(labelDiv);
            label.position.set(0, 1, 0); // 相对于模型的局部位置
            return label
        },
        addimg(img, fun) {
            const imgDiv = document.createElement('div');
            imgDiv.style.width = '25px';
            imgDiv.style.height = '25px';
            imgDiv.style.backgroundImage = `url(${img})`;
            imgDiv.style.backgroundSize = 'contain';
            imgDiv.style.backgroundRepeat = 'no-repeat';
            imgDiv.style.cursor = 'pointer'; // 鼠标悬停时显示为手型
            imgDiv.style.zIndex = '1000'
            imgDiv.addEventListener('click', fun)
            const imgLabel = new CSS3DObject(imgDiv);
            imgLabel.position.set(1, 0, 0);
            return imgLabel
        },
        init() {
            this.scene = new THREE.Scene();
            this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            this.renderer = new THREE.WebGLRenderer({ alpha: true });
            this.renderer.setSize(window.innerWidth, window.innerHeight);
            document.querySelector("#wbglg").appendChild(this.renderer.domElement);

            // 创建 CSS2DRenderer
            this.css2DRenderer = new CSS2DRenderer();
            this.css2DRenderer.setSize(window.innerWidth, window.innerHeight);
            this.css2DRenderer.domElement.style.position = 'absolute';
            this.css2DRenderer.domElement.style.top = '0px';
            this.css2DRenderer.domElement.style.left = '0px';
            this.css2DRenderer.domElement.style.pointerEvents = 'none'
            document.body.appendChild(this.css2DRenderer.domElement);

            // 创建 CSS3DRenderer
            this.css3DRenderer = new CSS3DRenderer();
            this.css3DRenderer.setSize(window.innerWidth, window.innerHeight);
            this.css3DRenderer.domElement.style.position = 'absolute';
            this.css3DRenderer.domElement.style.top = '0px';
            this.css3DRenderer.domElement.style.left = '0px';
            this.css3DRenderer.domElement.style.pointerEvents = 'none'
            document.body.appendChild(this.css3DRenderer.domElement);

            // 添加模型
            this.addmodel()

            // 光源
            const light = new THREE.DirectionalLight(0xffffff);
            light.position.set(20, 10, 1305);
            this.scene.add(light);

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

            // 加载场景控制插件
            this.controls = new OrbitControls(this.camera, this.renderer.domElement);
            this.controls.enableZoom = true; // 启用缩放
            this.controls.minDistance = 1; // 最小缩放距离
            this.controls.maxDistance = 100; // 最大缩放距离
            this.controls.enableDamping = true; // 启用阻尼效果
            this.controls.dampingFactor = 0.05; // 阻尼系数

            // 渲染循环
            this.animate();
        },
    },
}
</script>
<style scoped></style>
相关推荐
崔庆才丨静觅5 分钟前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax