在之前我们使用threejs绘制3d地图效果添加label标签,接下来我们需要当鼠标移入到指定区域后,当前区域地图高亮。
threejs 3d地图渲染完成后,我们还需要进行交互操作,比如鼠标移入到地图指定区域后,我们需要让地图高亮。
效果演示地址
监听移入操作
要实现移入高亮,我们首先要监听鼠标移入的动作。我们通过监听mousemove
来实现。
addEventListener('mousemove', choose); // 监听窗口鼠标滑动事件
射线拾取
当鼠标移入地图后,我们需要进行判断鼠标放在了地图哪个位置,需要哪个位置高亮,我们通过three的射线拾取方法 THREE.Raycaster()
来获取鼠标在地图的哪个位置
var raycaster = new THREE.Raycaster();
射线拾取获取网格模型对象
鼠标移入后,我们需要知道鼠标在当前div的横纵坐标,并且需要获取当前视图区域的宽和高。通过Raycaster
方法来判断是否有模型对象被获取到了,如果获取到了,则说明下面有模型,我们可以进行后续操作,比如给模型修改颜色。
function choose(event) {
var Sx = event.offsetX; //鼠标所在位置横坐标
var Sy = event.offsetY; //鼠标所在位置纵坐标
//屏幕坐标转WebGL标准设备坐标
var x = (Sx / that.$refs.canvasGLTFBody.offsetWidth) * 2 - 1; //WebGL标准设备横坐标
var y = -(Sy / that.$refs.canvasGLTFBody.offsetHeight) * 2 + 1; //WebGL标准设备纵坐标
//创建一个射线投射器`Raycaster`
var raycaster = new THREE.Raycaster();
//通过鼠标单击位置标准设备坐标和相机参数计算射线投射器`Raycaster`的射线属性.ray
raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
//返回.intersectObjects()参数中射线选中的网格模型对象
// 未选中对象返回空数组[],选中一个数组1个元素,选中两个数组两个元素
var intersects = raycaster.intersectObjects(meshGroup.children);
// console.log("射线器返回的对象", intersects);
// console.log("射线投射器返回的对象 点point", intersects[0].point);
// console.log("射线投射器的对象 几何体",intersects[0].object.geometry.vertices)
// intersects.length大于0说明,说明选中了模型
if (intersects.length > 0) {
chooseMesh = intersects[0].object;
} else {
chooseMesh = null;
}
}
修改选中模型的颜色
当我们选中到了模型,我们给模型设置一个其他的颜色,表示我们选中了模型。chooseMesh.material[0].color
我们通过修改模型的color属性来实现。
function choose(event) {
var Sx = event.offsetX; //鼠标单击位置横坐标
var Sy = event.offsetY; //鼠标单击位置纵坐标
//屏幕坐标转WebGL标准设备坐标
var x = (Sx / that.$refs.canvasGLTFBody.offsetWidth) * 2 - 1; //WebGL标准设备横坐标
var y = -(Sy / that.$refs.canvasGLTFBody.offsetHeight) * 2 + 1; //WebGL标准设备纵坐标
//创建一个射线投射器`Raycaster`
var raycaster = new THREE.Raycaster();
//通过鼠标单击位置标准设备坐标和相机参数计算射线投射器`Raycaster`的射线属性.ray
raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
//返回.intersectObjects()参数中射线选中的网格模型对象
// 未选中对象返回空数组[],选中一个数组1个元素,选中两个数组两个元素
var intersects = raycaster.intersectObjects(meshGroup.children);
// console.log("射线器返回的对象", intersects);
// console.log("射线投射器返回的对象 点point", intersects[0].point);
// console.log("射线投射器的对象 几何体",intersects[0].object.geometry.vertices)
// intersects.length大于0说明,说明选中了模型
if (intersects.length > 0) {
chooseMesh = intersects[0].object;
// chooseMesh.material.color.set(new THREE.Color(0xfec308));//选中改变颜色
// 根据鼠标位置设置标签位置(射线与mesh表面相交点世界坐标intersects[0].point)
// intersects[0].point.y += 1;//偏移
chooseMesh.material[0].color = color1;
chooseMesh.material[1].color = color1;
} else {
chooseMesh = null;
}
}
移入后高亮模型,移开后移出模型
当我们鼠标移入后高亮了模型,鼠标移开后,还需要将模型的颜色恢复到之前的颜色。
if (chooseMesh) {
// 把上次选中的mesh设置为原来的颜色
chooseMesh.material[0].color = new THREE.Color(0x6793e0);
chooseMesh.material[1].color = new THREE.Color(0x174ca3);
} else {
label.element.style.visibility = 'hidden';//没有选中mesh,隐藏标签
}
到这里鼠标移入高亮,移开恢复之前的颜色的效果就实现了。
完整实例代码
<template>
<div class="canvasGLTFBody" ref="canvasGLTFBody">
<div ref='canvasGLTF' class="canvasGLTF" id="canvasGLTF">
</div>
<div id="LoadingInfo" class="LoadingInfo"></div>
</div>
</template>
<script>
import * as THREE from 'three';
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js';
import {borderLine} from './js/line.js';
import {ExtrudeMesh} from './js/ExtrudeMesh.js';
import {getBoxCenter, getBoxSize} from "./js/centerCamera.js";
import {tag} from "./js/tag.js";
import {tagName} from "./js/tagName.js";
import data from './data/china.json'
import {CSS2DRenderer, CSS2DObject} from 'three/examples/jsm/renderers/CSS2DRenderer.js';
export default {
name: 'GLTFCanvas',
data() {
return {
list: []
}
},
created: function () {
},
mounted() {
var that = this;
const viewElem = document.body;
setTimeout(() => {
this.getData()
}, 300)
// 监听窗口变化
const resizeObserver = new ResizeObserver(() => {
setTimeout(() => {
that.getresize();
}, 300)
});
resizeObserver.observe(viewElem);
},
methods: {
getresize() {
var that = this;
this.width = this.$refs.canvasGLTF.offsetWidth
this.height = this.$refs.canvasGLTF.offsetHeight
that.renderer.setSize(that.width, that.height);
that.labelRenderer.setSize(this.width, this.height)
},
tag(name) {
// 创建div元素(作为标签)
var div = document.createElement('div');
div.style.visibility = 'hidden';
div.innerHTML = '测试信息';
div.style.padding = '5px 10px';
div.style.color = '#fff';
div.style.fontSize = '16px';
div.style.position = 'absolute';
div.style.backgroundColor = 'red';
div.style.borderRadius = '5px';
//div元素包装为CSS2模型对象CSS2DObject
var label = new CSS2DObject(div);
div.style.pointerEvents = 'none';//避免HTML标签遮挡三维场景的鼠标事件
// 设置HTML元素标签在three.js世界坐标中位置
// label.position.set(x, y, z);
return label;//返回CSS2模型标签
},
getData() {
var that = this
// GDP最高对应红色,GDP最低对应白色
var color1 = new THREE.Color(0xedc551);
var tagList = []
var scene = new THREE.Scene();
var mapGroup = new THREE.Group();
scene.add(mapGroup);
var lineGroup = new THREE.Group();
mapGroup.add(lineGroup);
var meshGroup = new THREE.Group();
var center =
mapGroup.add(meshGroup);
var TensileHeight = 0.1;// 拉伸高度
lineGroup.position.z = TensileHeight + TensileHeight * 0.1;
// 光源
var directionalLight = new THREE.DirectionalLight(0xffffff, 0.6);
directionalLight.position.set(400, 200, 300);
scene.add(directionalLight);
var directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.6);
directionalLight2.position.set(-400, -200, -300);
scene.add(directionalLight2);
var ambient = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambient);
// 光源
var width = this.$refs.canvasGLTFBody.offsetWidth; //窗口宽度
var height = this.$refs.canvasGLTFBody.offsetHeight; //窗口高度
var k = width / height;
var s = 0;
that.labelRenderer = new CSS2DRenderer();
that.labelRenderer.setSize(width, height);
that.labelRenderer.domElement.style.position = 'absolute';
// // 避免renderer.domElement影响HTMl标签定位,设置top为0px
that.labelRenderer.domElement.style.top = '0px';
that.labelRenderer.domElement.style.left = '0px';
// //设置.pointerEvents=none,以免模型标签HTML元素遮挡鼠标选择场景模型
that.labelRenderer.domElement.style.pointerEvents = 'none';
getJson();
// 添加tag标签
tagList.forEach((type) => {
scene.add(tagName(type.title, type.center[0], type.center[1], TensileHeight + 0.01));
});
s = (getBoxSize(mapGroup).x + getBoxSize(mapGroup).y) / 3
var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
camera.position.set(104, -105, 200);
camera.lookAt(scene.position);
that.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
// 设置背景颜色
that.renderer.setClearColor(0x000000, 0)
that.renderer.setSize(width, height);
document.body.appendChild(that.renderer.domElement);
function render() {
that.labelRenderer.render(scene, camera);
that.renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
var controls = new OrbitControls(camera, that.renderer.domElement);
// 获取中心点
var getBoxCenterData = getBoxCenter(mapGroup)
// 设置中心
controls.target.set(getBoxCenterData.x, getBoxCenterData.y, 0);
controls.update();
/**
* 射线投射器`Raycaster`的射线拾取选中网格模型对象函数choose()
* 选中的网格模型变为半透明效果
*/
var label = tag();
scene.add(label);//标签插入场景中
// console.log(label);
var chooseMesh = null;//标记鼠标拾取到的mesh
function choose(event) {
console.log(event)
if (chooseMesh) {
// 把上次选中的mesh设置为原来的颜色
chooseMesh.material[0].color = new THREE.Color(0x6793e0);
chooseMesh.material[1].color = new THREE.Color(0x174ca3);
} else {
label.element.style.visibility = 'hidden';//没有选中mesh,隐藏标签
}
var Sx = event.offsetX; //鼠标单击位置横坐标
var Sy = event.offsetY; //鼠标单击位置纵坐标
//屏幕坐标转WebGL标准设备坐标
var x = (Sx / that.$refs.canvasGLTFBody.offsetWidth) * 2 - 1; //WebGL标准设备横坐标
var y = -(Sy / that.$refs.canvasGLTFBody.offsetHeight) * 2 + 1; //WebGL标准设备纵坐标
//创建一个射线投射器`Raycaster`
var raycaster = new THREE.Raycaster();
//通过鼠标单击位置标准设备坐标和相机参数计算射线投射器`Raycaster`的射线属性.ray
raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
//返回.intersectObjects()参数中射线选中的网格模型对象
// 未选中对象返回空数组[],选中一个数组1个元素,选中两个数组两个元素
var intersects = raycaster.intersectObjects(meshGroup.children);
// console.log("射线器返回的对象", intersects);
// console.log("射线投射器返回的对象 点point", intersects[0].point);
// console.log("射线投射器的对象 几何体",intersects[0].object.geometry.vertices)
// intersects.length大于0说明,说明选中了模型
if (intersects.length > 0) {
chooseMesh = intersects[0].object;
// chooseMesh.material.color.set(new THREE.Color(0xfec308));//选中改变颜色
// 根据鼠标位置设置标签位置(射线与mesh表面相交点世界坐标intersects[0].point)
// intersects[0].point.y += 1;//偏移
chooseMesh.material[0].color = color1;
chooseMesh.material[1].color = color1;
} else {
chooseMesh = null;
}
}
addEventListener('mousemove', choose); // 监听窗口鼠标滑动事件
// 获取Json数据
function getJson() {
return new Promise(resolve => {
data.features.forEach(function (area) {
if (area.geometry.type === "Polygon") {
area.geometry.coordinates = [area.geometry.coordinates];
}
var listItem = {
title: area.properties.name,
center: area.properties.center,
}
tagList.push(listItem)
lineGroup.add(borderLine(area.geometry.coordinates));
var height = TensileHeight;//TensileHeight
var mesh = ExtrudeMesh(area.geometry.coordinates, height)
mesh.name = area.properties.name
meshGroup.add(mesh);
});
var lineGroup2 = lineGroup.clone();
mapGroup.add(lineGroup2);
lineGroup2.position.z = -TensileHeight * 0.1;
resolve('data')
})
}
// 鼠标移入时显示详细信息
// getMessage(camera, that.flyData, width, height)
that.$refs.canvasGLTF.appendChild(that.labelRenderer.domElement)
that.$refs.canvasGLTF.appendChild(that.renderer.domElement)
}
}
}
</script>
<style lang="scss" scoped>
.canvasGLTFBody {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
flex-direction: row;
align-content: flex-start;
}
.canvasGLTF {
position: relative;
width: 100%;
height: 100%;
}
.LoadingInfo {
width: 100%;
height: 100%;
position: absolute;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
flex-direction: row;
align-content: flex-start;
color: #fff;
z-index: 1000000;
top: 0;
left: 0;
font-size: 20px;
display: none;
}
</style>
<style>
.tagDiv {
/* width: 500px; */
position: absolute;
padding: 6px 20px;
background-color: rgba(255, 255, 255, 0.1);
color: #fff;
border-radius: 5px;
visibility: hidden;
}
</style>
完整实例代码下载
当前完整演示实例代码下载
项目基于Vue3 vite js实现