赞
踩
第一次接触threeJS,说实话,挺脑瓜子疼的!
功能:3D地球(纹理贴图),地球上添加标记点(经纬度),点击标记点弹出对应的信息框,地球入场动画,相机移动动画等。
先开效果图吧
一:添加必要的依赖
- yarn add three
- yarn add tween
- import * as THREE from "three"
- import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
- import * as TWEEN from "tween"
二:组件代码
- <template>
- <div style="width: 100%; height: 100%;">
- <div id="layerMain">
- <div>{{ countryName }}</div>
- <div class="shape"></div>
- </div>
- <div ref="mapId" style="width: 100%; height: 100%;"></div>
- </div>
- </template>
- <script>
- import * as THREE from "three"
- import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
- import * as TWEEN from "tween"
- import map_img from '../../assets/images/map.jpg'
- import map_wl from '../../assets/images/wl.png'
- let camera, scene, controls, mesh;
- let group = new THREE.Group();
- let radius = 70;
- let fov = 100;
- export default {
- name: 'index',
- data () {
- return {
- mapDom: null,
- renderer: null,
- animationType: true, // 地球入场动画
- rotationY: true, // 地球自动旋转
- meshAnimateType: false, // 标记点动画
- lonlat: { x: 0, y: 0, z: 200 },
- countryName: null, // 数据
- }
- },
- mounted () {
- this.info ()
- },
- methods: {
- // 初始化
- info () {
- this.infoThree ()
- this.infoBall ()
- this.infoRender ()
- this.renderer.domElement.addEventListener("click", this.infoMouse)
- },
-
- // 基本配置
- infoThree () {
- // 场景
- scene = new THREE.Scene()
- // 渲染
- this.renderer = new THREE.WebGLRenderer({
- antialias: true,
- })
- this.mapDom = this.$refs.mapId
- this.renderer.setSize(this.mapDom.clientWidth, this.mapDom.clientHeight)
- this.renderer.setClearColor(0x000, 0)
- this.mapDom.appendChild(this.renderer.domElement)
- // 相机
- camera = new THREE.PerspectiveCamera(
- fov,
- this.mapDom.clientWidth / this.mapDom.clientHeight,
- 1,
- 1000
- )
- camera.position.set(0, 0, 200)
- camera.lookAt(0, 0, 0)
- // 鼠标
- this.infoOrbitControls()
- },
-
- // 重新渲染
- infoRender() {
- this.renderer.clear()
- // 地球入场动画
- if (this.animationType) this.ballAnimation()
- // 地球旋转
- if (this.rotationY) this.ballRotationY()
- // 标记点动画
- if (this.meshAnimateType) this.meshAnimate()
- this.renderer.render(scene, camera)
- requestAnimationFrame(this.infoRender)
- TWEEN.update()
- },
-
- // 鼠标
- infoOrbitControls() {
- controls = new OrbitControls(camera, this.renderer.domElement)
- controls.enableDamping = true
- controls.enableZoom = true
- controls.autoRotate = false
- controls.autoRotateSpeed = 2
- controls.enablePan = true
- },
-
- // 地球
- infoBall() {
- // 纹理贴图
- let textureLoader = new THREE.TextureLoader();
- textureLoader.load(map_img, function (texture) {
- // 创建球
- let geometry = new THREE.SphereGeometry(radius, 100, 100);
- let material = new THREE.MeshBasicMaterial({
- map: texture, //设置颜色贴图属性值
- });
- //网格模型对象Mesh
- mesh = new THREE.Mesh(geometry, material);
- // 唯一标识
- mesh.name = "ballMain";
- // 添加到场景中
- scene.add(mesh);
- });
- },
-
- // 地球入场动画
- ballAnimation() {
- fov -= 0.6
- if (fov <= 45) {
- this.animationType = false
- camera.position.set(0, 0, 200)
- camera.lookAt(0, 0, 0)
- this.infoOrbitControls()
- } else {
- camera = new THREE.PerspectiveCamera(
- fov,
- this.mapDom.clientWidth / this.mapDom.clientHeight,
- 1,
- 1000
- );
- camera.position.set(0, 0, 200)
- camera.lookAt(0, 0, 0)
- }
- },
-
- // 地球自动旋转
- ballRotationY() {
- scene.rotation.y += 0.003
- },
-
- // 添加纹理标记点
- infoMark(item) {
- console.log(group)
- let cityGeometry = new THREE.PlaneBufferGeometry(1, 1) //默认在XOY平面上
- let textureLoader = new THREE.TextureLoader()
- let texture = textureLoader.load(map_wl)
- let cityWaveMaterial = new THREE.MeshBasicMaterial({
- color: item.color,
- map: texture,
- transparent: true,
- opacity: 0,
- side: THREE.DoubleSide
- })
- let mesh = new THREE.Mesh(cityGeometry, cityWaveMaterial)
- const coord = this.lon2xyz(radius * 1.01, item.lon, item.lat)
- mesh.scale.set(2, 2, 2)
- // 唯一标识
- mesh.name = item.name
- mesh.privateType = 'mark'
- mesh.position.set(coord.x, coord.y, coord.z)
- const coordVec3 = new THREE.Vector3(
- coord.x,
- coord.y,
- coord.z
- ).normalize()
- const meshNormal = new THREE.Vector3(0, 0, 1)
- mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3)
- if (scene.getObjectByName(item.name) === undefined) {
- group.add(mesh)
- //网格模型添加到场景中
- scene.add(group)
- this.meshAnimateType = true
- }
- },
-
- // 标记点动画
- meshAnimate() {
- for (let i = 0; i < group.children.length; i++) {
- if (group.children[i].privateType === "mark") {
- // 添加初始随机数,防止动画同步
- group.children[i].material.opacity += Math.random() * 0.05
- group.children[i].scale.set(
- group.children[i].material.opacity + 7,
- group.children[i].material.opacity + 7,
- group.children[i].material.opacity + 7
- )
- if (group.children[i].scale.x >= 9) {
- group.children[i].material.opacity = 0
- }
- }
- }
- },
-
- // 移动相机
- cameraPos (objList) {
- this.frameDivClose ()
- let layerObj = scene.getObjectByName(objList.name)
- if (layerObj) {
- scene.rotation.y = 0
- this.rotationY = false
- new TWEEN.Tween( { x: this.lonlat.x, y: this.lonlat.y, z: this.lonlat.z } )
- .to( { x: layerObj.position.x * 2.8, y: layerObj.position.y * 2.8, z: layerObj.position.z * 2.8}, 1500 )
- .onUpdate( function () {
- camera.position.x = this.x
- camera.position.y = this.y
- camera.position.z = this.z
- camera.lookAt(0, 0, 0)
- })
- .onComplete ( ()=> {
- this.retrievalLayer (objList.name)
- })
- .easing(TWEEN.Easing.Sinusoidal.InOut)
- .start()
- this.lonlat = camera.position
- // 弹窗面板赋值
- this.countryName = objList.name
- } else {
- console.log('图层数据已被全部删除,请重新刷新界面,或者重新调用数据初始化方法: this.infoMap ()')
- alert('图层数据已被全部删除,请重新刷新界面,或者重新调用数据初始化方法: this.infoMap ()')
- }
- },
-
- // 检索指定的图层
- retrievalLayer (name) {
- let layerObj = scene.getObjectByName(name)
- this.infoDiv(layerObj.position.x, layerObj.position.y, layerObj.position.z)
- },
-
- // 鼠标事件(点击标记的点的事件)
- infoMouse(event) {
- event.preventDefault();
- const raycaster = new THREE.Raycaster();
- const mouse = new THREE.Vector2();
- // 通过鼠标点击位置,计算出 raycaster 所需点的位置,以屏幕为中心点,范围 -1 到 1
- let getBoundingClientRect = this.mapDom.getBoundingClientRect();
- mouse.x =
- ((event.clientX - getBoundingClientRect.left) /
- this.mapDom.offsetWidth) *
- 2 -
- 1;
- mouse.y =
- -(
- (event.clientY - getBoundingClientRect.top) /
- this.mapDom.offsetHeight
- ) *
- 2 +
- 1;
- //通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
- raycaster.setFromCamera(mouse, camera);
- // 获取与射线相交的对象数组,其中的元素按照距离排序,越近的越靠前
- let intersects = raycaster.intersectObjects(scene.children);
- // 点击对象的处理
- for (let i = 0; i < intersects.length; i++) {
- if (intersects[i].object.name !== 'ballMain') {
- // 弹窗面板赋值
- this.countryName = intersects[i].object.name
- let objList = {
- name: intersects[i].object.name
- }
- this.cameraPos (objList)
- return false
- } else {
- // 开启自动旋转
- this.rotationY = true
- this.frameDivClose ()
- }
- }
- },
-
- // 标签
- infoDiv(pointx, pointy, pointz) {
- // 坐标转换
- let world_vector = new THREE.Vector3(
- pointx,
- pointy,
- pointz
- )
- let vector = world_vector.project(camera)
- let halfWidth = this.mapDom.offsetWidth / 2,
- halfHeight = this.mapDom.offsetHeight / 2
- let x = Math.round(vector.x * halfWidth + halfWidth)
- let y = Math.round(-vector.y * halfHeight + halfHeight)
- //创建div容器
- let moonDiv = document.getElementById("layerMain")
- moonDiv.style.display = "block"
- moonDiv.style.left = x - 150 + "px"
- moonDiv.style.top = y - 180 + "px"
- },
-
- // 关闭标签
- frameDivClose() {
- let divHtml = document.getElementById("layerMain")
- divHtml.style.display = "none"
- },
-
- // 添加光柱
- infoColumn (item) {
- const material = new THREE.MeshBasicMaterial({
- color: item.color,
- transparent: true,
- opacity: .9,
- side: THREE.DoubleSide
- })
- const coord = this.lon2xyz(radius * 1.01, item.lon, item.lat)
- const coordVec3 = new THREE.Vector3(coord.x, coord.y, coord.z).normalize()
- const geometry = new THREE.CylinderGeometry(0.2, 2.8, 30)
- const mesh = new THREE.Mesh(geometry, material)
- mesh.name = item.name
- mesh.privateType = 'column'
- mesh.position.set(coord.x, coord.y, coord.z)
- mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), coordVec3)
- group.add(mesh)
- scene.add(group)
- },
-
- // 删除所有标记点
- delAll () {
- this.frameDivClose ()
- group.traverse((item) => {
- if (item.type === 'Mesh') {
- item.geometry.dispose()
- item.material.dispose()
- }
- })
- scene.remove(group)
- // 删除group中的children
- if (group.children && group.children.length > 0) {
- let i = 0
- for (i; i < group.children.length; i++) {
- group.remove(group.children[i])
- }
- }
- },
-
- // 删除指定标记点
- delMark (item) {
- this.frameDivClose ()
- let layerObj = scene.getObjectByName(item.name)
- group.remove(layerObj)
- },
-
- // 经纬度转坐标
- lon2xyz(R, longitude, latitude) {
- const lon = (Number(longitude) + 90) * (Math.PI / 180)
- const lat = Number(latitude) * (Math.PI / 180)
- const x = R * Math.cos(lat) * Math.sin(lon)
- const y = R * Math.sin(lat)
- const z = R * Math.cos(lon) * Math.cos(lat)
- return { x, y, z }
- },
- }
- }
- </script>
-
- <style lang="scss">
- #layerMain {
- position: absolute;
- width: 300px;
- height: 160px;
- line-height: 160px;
- text-align: center;
- color: white;
- display: none;
- background-color: rgba(34,34,35,.6);
- .shape {
- position: absolute;
- margin: auto;
- left: 0;
- right: 0;
- width: 0;
- height: 0;
- bottom: -40px;
- border: 20px solid transparent;
- border-top-color: rgba(34,34,35,.6);
- }
- }
- </style>
三:父组件中的代码
- <template>
- <div class="home">
- <div class="rightMain">
- <div class="title">跳转操作</div>
- <div class="cont">
- <ul>
- <li v-for="(item, index) in objList" @click="cameraPos(item)">{{ item.name }}</li>
- </ul>
- </div>
- <div class="title">其他操作</div>
- <div class="cont">
- <ul>
- <li @click="rotationChange">{{ rotation }}</li>
- <li @click="columnChange">添加光柱</li>
- <li @click="delAllChange">删除所有</li>
- <li @click="delMarkChange">删除美国标记点</li>
- </ul>
- </div>
- <div class="title">重置操作</div>
- <div class="cont">
- <ul>
- <li @click="reset">初始化数据</li>
- </ul>
- </div>
- </div>
- <threeIndex ref="threeMapId"></threeIndex>
- </div>
- </template>
-
- <script>
- import threeIndex from '../components/three/Index'
- export default {
- name: 'HomeView',
- components: {
- threeIndex
- },
- data () {
- return {
- objList: [
- { lon: 116.358976, lat: 39.803282, name: "中国", color: '#1FFBC6' },
- { lon: 139.812263, lat: 35.677294, name: "日本", color: '#A41FE8'},
- { lon: 77.198596, lat: 28.575136, name: "印度", color: '#E8BB1F' },
- { lon: -77.02238, lat: 38.900042, name: "美国", color: '#E81F56' },
- { lon: 31.266092, lat: 30.085626, name: "埃及", color: '#1FFBC6' },
- { lon: 103.813654, lat: 1.291125, name: '新加坡', color: '#E8BB1F' },
- { lon: -47.930912, lat: -15.781949, name: '巴西', color: '#A41FE8' },
- { lon: 149.130214, lat: -35.318235, name: '澳大利亚', color: '#E81F56' }
- ],
- objList_2: [
- { lon: 116.358976, lat: 39.803282, name: "中国column", color: '#1FFBC6' },
- { lon: 139.812263, lat: 35.677294, name: "日本column", color: '#A41FE8'},
- { lon: 77.198596, lat: 28.575136, name: "印度column", color: '#E8BB1F' },
- { lon: -77.02238, lat: 38.900042, name: "美国column", color: '#E81F56' },
- { lon: 31.266092, lat: 30.085626, name: "埃及column", color: '#1FFBC6' },
- { lon: 103.813654, lat: 1.291125, name: '新加坡column', color: '#E8BB1F' },
- { lon: -47.930912, lat: -15.781949, name: '巴西column', color: '#A41FE8' },
- { lon: 149.130214, lat: -35.318235, name: '澳大利亚column', color: '#E81F56' }
- ],
- rotation: '关闭旋转'
- }
- },
- mounted () {
- this.infoMap ()
- },
- methods: {
- // 重置
- reset () {
- this.infoMap ()
- },
-
- // 地球添加标记点
- infoMap() {
- for (let i = 0; i < this.objList.length; i++) {
- this.$refs.threeMapId.infoMark(this.objList[i]);
- }
- },
-
- // 移动相机
- cameraPos(item) {
- this.$refs.threeMapId.cameraPos(item);
- },
-
- // 开启或关闭地球自动旋转
- rotationChange () {
- this.$refs.threeMapId.rotationY = !this.$refs.threeMapId.rotationY
- this.rotation = this.$refs.threeMapId.rotationY === true?'关闭旋转':'开启旋转'
- this.$refs.threeMapId.frameDivClose()
- },
-
- // 添加光柱infoColumn
- columnChange () {
- for (let i = 0; i < this.objList_2.length; i++) {
- this.$refs.threeMapId.infoColumn(this.objList_2[i]);
- }
- },
-
- // 删除所有标记点
- delAllChange () {
- for (let i = 0; i < this.objList.length; i++) {
- this.$refs.threeMapId.delAll(this.objList[i]);
- }
- for (let i = 0; i < this.objList_2.length; i++) {
- this.$refs.threeMapId.delAll(this.objList_2[i]);
- }
- },
-
- // 删除指定标记点
- delMarkChange () {
- let item = {
- name: '美国'
- }
- this.$refs.threeMapId.delMark(item)
- },
- }
- }
- </script>
-
- <style lang="scss">
- .home {
- position: relative;
- width: 100%;
- height: 100%;
- overflow: hidden;
- background-image: url("../assets/images/back.png");
- background-size: 100% 130%;
- .rightMain {
- position: absolute;
- right: 0;
- width: 300px;
- height: 100%;
- z-index: 100;
- padding: 10px;
- box-sizing: border-box;
- color: white;
- background-color: rgba(255,255,255, .2);
- .title {
- width: 100%;
- font-size: 18px;
- font-weight: bold;
- margin-bottom: 10px;
- }
- .cont {
- height: 150px;
- ul {
- padding: 0;
- margin-bottom: 0;
- li {
- list-style: none;
- float: left;
- width: 33.33%;
- padding: 10px 0;
- text-align: center;
- cursor: pointer;
- font-size: 14px;
- &:hover {
- color: aquamarine;
- }
- }
- }
- }
- }
- }
- </style>
四:项目gitee地址
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。