当前位置: 首页 > news >正文

Three.js 分级地图教程

分级地图 ·Area Map· ▶ 在线运行案例

  • 案例合集:三维可视化功能案例(threehub.cn)
  • 开源仓库github地址:https://github.com/z2586300277/three-cesium-examples
  • 400个案例代码:网盘链接

你将学到什么

  • OrbitControls 相机轨道交互
  • GSAP 时间轴与补间动画
  • Raycaster 鼠标拾取与交互
  • BufferGeometry 自定义顶点/索引数据
  • CSS2D/3D 标签 DOM 叠加
  • requestAnimationFrame渲染循环与resize自适应

效果说明

本案例演示分级地图效果:支持鼠标拾取、绘制或拖拽交互,GeoJSON 行政区/路线数据可视化贴地展示;核心用到 OrbitControls、GSAP、Raycaster。建议先打开文首在线案例查看动态画面,再对照下方源码逐步理解。

核心概念

  • Scene / Camera / WebGLRenderer构成最小渲染闭环;大场景可开logarithmicDepthBuffer缓解 Z-fighting。
  • OrbitControls提供轨道旋转/缩放;开启enableDamping后需在 animate 中controls.update()
  • Raycaster将屏幕坐标转为射线,与场景求交得到世界坐标,常用于绘制/拾取。

实现步骤

  • 搭建灯光与环境(如有)
  • requestAnimationFrame 循环 update + render
  • 代码要点

    import * as THREE from 'three'

    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js' import gsap from 'gsap'

    const PAEAMS = { DEPTH: 15, coordsMaxCounts: 500 } const fetchJson = (url) => fetch(url).then((res) => res.json())

    setScene(document.getElementById('box'))

    async function setScene(DOM) {

    const scene = new THREE.Scene() const camera = new THREE.PerspectiveCamera(50, DOM.clientWidth / DOM.clientHeight, 0.1, 100000000) camera.position.set(0, 600, 300) const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, logarithmicDepthBuffer: true }) renderer.setSize(DOM.clientWidth, DOM.clientHeight) DOM.appendChild(renderer.domElement) const controls = new OrbitControls(camera, renderer.domElement) controls.enableDamping = true const light = new THREE.DirectionalLight(0xffffff, 1) light.position.set(100, 100, 100) scene.add(new THREE.AmbientLight(0xffffff, 1.5), light) const css2DRender = new CSS2DRenderer() css2DRender.setSize(DOM.clientWidth, DOM.clientHeight) css2DRender.domElement.style.zIndex = 0 css2DRender.domElement.style.position = 'relative' css2DRender.domElement.style.top = -DOM.clientHeight + 'px' css2DRender.domElement.style.height = DOM.clientHeight + 'px' css2DRender.domElement.style.width = DOM.clientWidth + 'px' css2DRender.domElement.style.pointerEvents = 'none' DOM.appendChild(css2DRender.domElement) animate() function animate() { controls.update() css2DRender.render(scene, camera) requestAnimationFrame(animate) renderer.render(scene, camera) }

    // 根级 const rootGeoJson = await fetchJson('https://z2586300277.github.io/3d-file-server/files/json/china.json') const rootGroup = createGeo(rootGeoJson) rootGroup.rotation.x = -Math.PI / 2 rootGroup.position.sub(new THREE.Box3().setFromObject(rootGroup).getCenter(new THREE.Vector3())) // 平移到原点 scene.add(rootGroup) clickEvent(camera, controls, DOM, rootGroup) // 点击事件

    }

    // 点击事件 function clickEvent(camera, controls, DOM, rootGroup) {

    const raycaster = new THREE.Raycaster() const mouse = new THREE.Vector2() const getLavelObj = (obj) => { if (obj.info) return obj if (obj.parent) return getLavelObj(obj.parent) } DOM.addEventListener('click', async (e) => {

    mouse.x = (e.clientX / DOM.clientWidth) * 2 - 1 mouse.y = -(e.clientY / DOM.clientHeight) * 2 + 1 raycaster.setFromCamera(mouse, camera) const intersects = raycaster.intersectObjects(rootGroup.children, true) if (intersects.length > 0) {

    const item = getLavelObj(intersects[0].object) const info = item.info const json = await fetchJson(https://z2586300277.github.io/3d-file-server/geojson/${info.properties.adcode}.json) if (!json.features.length) return item.label.visible = false // 隐藏文字

    // 子级组 const child_geo = createGeo(json) gsap.to(child_geo.position, { z: PAEAMS.DEPTH, duration: 1.5, ease: 'power2.out' }) item.child_geo = child_geo item.add(child_geo) } }) }

    // 创建地图 const texture = new THREE.TextureLoader().load('https://z2586300277.github.io/three-editor/dist/files/channels/texture.png') texture.wrapT = THREE.RepeatWrapping texture.repeat.y = 0.1 function animateTexture() { texture.offset.y += 0.03 requestAnimationFrame(animateTexture) } animateTexture()

    function createGeo(geoJson) {

    const group = new THREE.Group() group.info = geoJson

    // 材质 const materials = [ new THREE.MeshStandardMaterial({ color: Math.random() * 0xffffff, transparent: true, metalness: 0.5, roughness: 0.5 }), new THREE.MeshBasicMaterial({ map: texture, color: 0xffffff * Math.random() }), ]

    // 遍历添加各个地块 geoJson.features.forEach((info) => {

    // 省会/中心坐标转换 if (info?.properties.center) info.properties.centerCoord3 = coordToVector(info.properties.center, 3) if (info?.properties?.centroid) info.properties.centroidCoord3 = coordToVector(info.properties.centroid, 3)

    // 行政区块组 const areaGroup = new THREE.Group() areaGroup.info = info areaGroup.name = info.properties?.name group.add(areaGroup)

    if (info.geometry.type === 'MultiPolygon') info.geometry.coordinates.forEach((j) => j.forEach((z) => areaGroup.add(createShapeWithCoord(z, materials)))) else if (info.geometry.type === 'Polygon') info.geometry.coordinates.forEach((j) => areaGroup.add(createShapeWithCoord(j, materials)))

    const div = document.createElement('div') div.innerHTML = info.properties?.name div.style.color = '#dbdbdb' div.style.fontSize = '10px' const css = new CSS2DObject(div)

    // 判断位置 if (info?.properties?.centroidCoord3 || info?.properties?.centerCoord3) css.position.copy(info?.properties?.centroidCoord3 || info?.properties?.centerCoord3) else { const box = new THREE.Box3().setFromObject(areaGroup) const center = box.getCenter(new THREE.Vector3()) css.position.copy(center) }

    css.position.z += PAEAMS.DEPTH || 2 areaGroup.label = css areaGroup.attach(css)

    })

    return group }

    // 坐标转换 function coordToVector(coord, type = 2, slace = 10000) { const [lng, lat] = coord const x = lng * 20037508.34 / 180 let y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180) y = y * 20037508.34 / 180 return type === 2 ? new THREE.Vector2(x / slace, y / slace) : new THREE.Vector3(x / slace, y / slace, 0) }

    function createShapeWithCoord(coordinates, materials) {

    while (coordinates.length > PAEAMS.coordsMaxCounts) coordinates = coordinates.filter((_, i) => i % 2 === 0)

    // 参数 const path = new THREE.Path(coordinates.map((k) => coordToVector(k))) const parameters = { bevelThickness: 0, bevelSize: 0, bevelOffset: 0, depth: PAEAMS.DEPTH || 2, bevelEnabled: true }

    // 边缘 // const emptyShape = new THREE.Shape() // emptyShape.holes.push(path) // const emptyGeometry = new THREE.ExtrudeGeometry(emptyShape, { depth: 0.0001, bevelEnabled: false }) // const emptyMaterial = new THREE.MeshBasicMaterial({ color: 0xbbbaba, transparent: true, opacity: 1, wireframe: true }) // const emptyMesh = new THREE.Mesh(emptyGeometry, emptyMaterial) const emptyGeometry = new THREE.BufferGeometry().setFromPoints(path.getPoints(1000)) const lineMaterial = new THREE.LineBasicMaterial({ color: 0xffffff, linewidth: 2, transparent: true, depthTest: false, }); const emptyMesh = new THREE.Line(emptyGeometry, lineMaterial); emptyMesh.position.z += parameters.depth + 0.01

    // 地块 const shape = new THREE.Shape() shape.curves.push(path) const geometry = new THREE.ExtrudeGeometry(shape, parameters)

    const mesh = new THREE.Mesh(geometry, materials) mesh.add(emptyMesh)

    return mesh

    }

    完整源码:GitHub

    小结

    • 本文提供分级地图完整 Three.js 源码与在线 Demo,建议先运行案例再改 uniform/参数做二次实验
    • 更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库
http://www.cnnetsun.cn/news/3095406.html

相关文章:

  • 暗黑4Data Retrieval Error报错/找不到d3d12.dll?两步操作!轻松解决不重装
  • threejs + vite + vue3 数字孪生简单案例
  • SPT-AKI存档编辑器终极指南:5分钟掌握离线版塔科夫存档修改全技巧
  • ClickHouse 慢查询怎么分析?我做了一个 EXPLAIN 可视化 + AI 调优助手
  • 2026 抖店一件代发拍单软件选购完整指南|筛选标准 + 避坑要点,选对工具自动发货更省事
  • 2026法国名义雇主EOR服务权威推荐榜单
  • 数字化转型下的许可优化:企业竞争新优势
  • 如何用UABEA彻底改变你的Unity资源编辑体验:从入门到精通的完整指南
  • Base Node:自己跑一个以太坊 L2 节点
  • AI在互联网开发工作中的应用
  • 2026年GEO生成式引擎优化服务商全景深度剖析
  • 如何解决视频生成中衣服和群众问题
  • 轻松搞定论文:6款2026年顶尖AI论文软件深度测评
  • MySQL 迁移实战——如何实现真正的“零改造“平滑切换
  • 8050和8550三极管参数
  • 《2026最新实测10款AI直播工具:告别深夜盯播,哪款更适合商家直播?》
  • nvm与nrm安装使用指南:提升Node.js开发效率
  • ClaudeCode最新版本安装
  • 嵌入式安全网关:A5000加密芯片与PIC18F微控制器的实战应用
  • Ubuntu 18.04 上 ROS1 Melodic 安装配置教程
  • 2.0T 高导磁芯 + IP68 防护 亿磁通 CT 取电技术突破宽工况应用瓶颈
  • 墨香情手游官方下载:多层幽界秘境寻宝获取绝版国风限定时装外观
  • 外网访问OpenWrt
  • AI算力盒子工作原理解析:边缘端AI推理的实现逻辑全拆解
  • GPT-5.5 中的测试时计算扩展:技术原理与产业影响
  • Bryntum Scheduler Pro 7.3.3 专业日程安排组件
  • 国产大模型 × 魔珐星云:从纯文本 Agent 到具身交互智能的实践
  • 蒸馏技术让4步生成高保真图像
  • 多协议标签交换MPLS
  • 智能硬件产品开发哪家好?服务商盘点