threejs 3d地图鼠标移入地图后高亮

threejs 3d地图 yekong

在之前我们使用threejs绘制3d地图效果添加label标签,接下来我们需要当鼠标移入到指定区域后,当前区域地图高亮。

threejs 3d地图鼠标移入地图后高亮

threejs 3d地图渲染完成后,我们还需要进行交互操作,比如鼠标移入到地图指定区域后,我们需要让地图高亮。

效果演示地址

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实现

相关文件下载地址
此资源需支付 ¥1 后下载
支付宝购买扫右侧红包码购买更优惠,如无法下载请联系微信:17331886870
喜欢
threejs 3d地图鼠标移入地图后高亮