先放一下预览图

主要使用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 = '×'; // 叉号
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>