threesmap

纯 three.js 实现的 Vue 3 3D 地图组件,不依赖 TresJS / R3F,内置示例数据、贴图与图层开关,零配置即可运行。
906
Install
npm install threesmap
Latest Version:0.4.0
Vue:3
License:MIT
Last Updated:Sep 27, 2026
Links: Gitee  ·  npm
Maintainer: sunlife_npm

threesmap

纯 three.js 实现的 Vue 3 3D 地图组件,不依赖 TresJS / R3F 等任何 Vue 3D 框架。 内置示例数据与贴图,零配置即可运行;支持省 → 市下钻、区县信息卡与企业布点聚合。

  • 纯 three.js,不侵入你的技术栈
  • 内置安徽省示例 GeoJSON + 8 张贴图,装完即用(无配套法线贴图)
  • 下钻:点击区域进入该市的区县视图,面包屑返回,镜头平滑过渡
  • 企业布点:DOM 标点 + 悬浮卡片 + 点击聚焦 + 按屏幕像素自动聚合
  • 图层开关:云层 / 边界流光 / 底部光环 / 数据柱 / 热力图 / 企业打点 / 是否允许下钻
  • 完整 TypeScript 类型;卸载时自动释放贴图、几何体、材质、渲染器与 DOM

pn3KdTf.md.png

安装

npm i threesmap
npm i three vue          # 这两个是 peer 依赖,需要你自己装
依赖 版本 说明
vue ^3.3 peer dependency
three >= 0.160 < 0.190 peer dependency。已在 0.186 验证,0.160 ~ 0.18x 均可用;打包时必须 external,否则会出现两份 three 实例
gsap、d3-geo —— 随包自动安装
heatmap.js v2.0.5 已内置(vendored,12 KB 随包打包):热力图开箱即用,无需额外安装。见根目录 LICENSE 中对原作者 Patrick Wied 的署名

three 的版本区间为什么不用 ^0.160:semver 对 0.x 的 caret 只允许补丁位变化, ^0.160.0 实际等于 >=0.160.0 <0.161.0,会把 0.161 ~ 0.18x 全挡在外面。

本包只提供 ESM 产物,需通过打包工具使用:Vite(含 Vitest / Nuxt 等)、 webpack 5+、rspack、esbuild 均可;内置贴图与示例数据是按需加载的独立分包, 因此不支持 webpack 4 及更早版本,也不支持 require() 直接引入。

不想走 npm? 也可以把 src/ThreeMap/ 整个目录拷进你的工程直接用 (import ThreeMap from "@/components/ThreeMap/ThreeMap.vue")。该目录是自包含的: 只需 three / gsap / d3-geo,零额外构建配置,热力图引擎已 vendored 在内。 细节与注意事项见 src/ThreeMap/README.md。

产物结构

内置贴图与示例数据不进主产物,用到才下载:

内容 体积 加载时机
组件代码 threesmap.mjs 约 119 KB(gzip 38 KB) 随你的应用一起打包(含内置热力图引擎 heatmap.js 12 KB)
内置贴图(8 张) 约 1.4 MB 挂载后按需下载,每张一个分包;只加载你没覆盖的 key
内置 GeoJSON(省 / 区县) 约 0.13 MB / 0.82 MB 用到才下载(16 市 / 104 区县)
  • 不传任何 props 时,主产物只有百 KB 量级的代码,贴图与大地图数据都是网络按需加载;
  • 想连这 1.4 MB 内置资产都不要?用精简入口 threesmap/core(约 104 KB,零内置数据与贴图):
// 换省份 / 换国家的业务侧推荐:不会为用不到的内置安徽资产买单
import ThreeMap from "threesmap/core";
import "threesmap/style.css"; // 样式与主入口共用同一份
// 必须自己传 mapData(贴图可选),未传时组件给出开发期告警

不想经 npm、想把源码整目录拷进项目?差异说明见 src/ThreeMap/README.md——除导入路径与样式引入方式外,用法与本手册一致。


30 秒跑通

<template>
  <div class="map-page">
    <ThreeMap />
  </div>
</template>

<script setup lang="ts">
import ThreeMap from "threesmap";
import "threesmap/style.css";
</script>

<style scoped>
/* 父容器必须有明确高度:组件内部是 100% × 100% */
.map-page {
  width: 100%;
  height: 100vh;
}
</style>

就这一步。不传任何 props 时用的是内置的安徽省数据与贴图。


功能导航

想要的效果 看这一节
数据怎么配才不会"不显示"(key 规则、量级约定) 0. 数据契约
换成自己的地图 / 业务数据 / 贴图 1. 基础地图
图层开关、工具栏 2. 图层开关
点击区域下钻到下一级 3. 下钻
下级区域 hover 显示业务数据 4. 下级区域信息
在地图上打企业点、聚合、点击 5. 企业布点
迁徙飞线、流光配色、自定义提示 6. 迁徙飞线
查 props / 事件 / 实例方法 / 动态更新 API 速查
控制台那条 [ThreeMap] 告警是什么意思 控制台告警速查
性能开销、畸形数据容错、dispose() 契约 注意事项

0. 数据契约

这一节是最容易踩坑的地方:配错了组件不会报错,只是"那块东西不显示"。 好在开发期组件会直接把这些情况打到控制台(前缀 [ThreeMap]),照着提示改即可。

key 规则

三类业务数据的 key 统一为 adcode:

数据 key 说明
mapHoverData(顶层悬浮卡) adcode,如 "340100" 顶层要素(市级)的 adcode
subMapHoverData(下级悬浮卡) adcode,如 "340102" 下级要素(区县级)的 adcode
enterpriseData / subEnterpriseData adcode 同上

区域名(properties.name)只用于展示(地图标签、卡片标题),不做 key—— 名称会跨市重名(比如多个"城关区"),adcode 才是稳定标识。 误把名称当 key 时控制台会给出 map-hover-data 的 key 应该是 adcode(如 "340100"),但检测到名称 key:… 的提示, 并列出当前层可用的 adcode 示例,直接照抄即可。

量级约定

字段 要求 配错的后果
mapHoverData[adcode].population 单位为万(2000 = 2000 万)。柱高 = population / 50 数值太小(比如 20)时柱子比地图自身厚度还矮,看起来像"没有数据柱"
热力图点的 value 1000 ~ 2000(强度映射区间在热力图层内固定) 低于 1000 会被压成最弱强度,几乎看不见

顶面贴图

贴图必须严格按顶层 GeoJSON 的经纬度包围盒裁剪(含所有岛屿、无留白、纵横比与投影后一致)—— UV 是按包围盒归一化映射的,范围不一致就会"只贴对一部分"。 想先确认几何对不对,把 textures.map 传 null 用纯色顶面看一眼最快。

什么时候传数据

mapData / subMapData / 悬浮卡 / 企业数据要求初始化时传入; 飞线与热力图支持运行时替换(见 API 速查 的"动态更新"), 其余数据如需运行时更换,用实例方法 setSubMapData,或重建组件。


1. 基础地图

传了就用你的,没传的继续用内置默认值。

<template>
  <ThreeMap
    :map-data="myMapData"
    :map-hover-data="myHoverData"
    :textures="{ map: myMapTexture }"
    :depth="6"
    background="#fff5e8"
  />
</template>

<script setup lang="ts">
import ThreeMap from "threesmap";
import type { CityGeoJSON, CityInfo } from "threesmap";
import "threesmap/style.css";

import myMapJson from "@/data/myMap.json";
import myMapTexture from "@/assets/my_map.png";

const myMapData = myMapJson as unknown as CityGeoJSON;
// key 是**要素的 adcode**(不是区域名),内容决定悬浮卡文字与数据柱高度
const myHoverData: Record<string, CityInfo> = {
  340100: { population: 963, gdp: "1.27万亿", area: "11445平方公里" },
};
</script>

GeoJSON 数据要求

  • 必须是 FeatureCollection;几何支持 MultiPolygon 与 Polygon,GeometryCollection 会被自动展平(内部统一成 MultiPolygon)
  • properties 至少要提供:
字段 用途
name 区域名,用于地图标签与卡片标题(不做业务数据的 key,key 一律用 adcode)
centroid 或 center [经度, 纬度],用于确定标签、数据柱的位置
adcode 行政区划代码。下钻、区县数据、企业数据都靠它关联,强烈建议提供
parent.adcode 上级 adcode。下钻时默认据此找下级,没有则用 resolveChildren 自定义

贴图

textures 可以只覆盖部分 key,其余继续用内置图——被覆盖的 key 不会去下载对应的内置图:

import myMapTexture from "@/assets/my_map.png";

// 只换地图主贴图:其余 7 张内置图照常按需下载
<ThreeMap :textures="{ map: myMapTexture }" />

全部 9 个 key:map、normalMap、barQuan、bottomGaoguang、bottomGrid、bottomGridBlack、bottomRotation1、bottomRotation2、cloud。 内置示例只提供其中 8 个(normalMap 为空:示例没有配套法线贴图); 需要程序化拿到内置图片地址时用 loadDefaultTextures()(异步,可只取子集):

import { loadDefaultTextures } from "threesmap";

const textures = await loadDefaultTextures(["map", "cloud"]); // 只想复用这两张
// <ThreeMap :textures="{ ...textures, bottomGrid: null }" />  null = 显式禁用某张内置贴图

每个 key 的 wrap / colorSpace / repeat 属于材质固有配置,已内置,外部只需给图片地址。地址支持相对路径、import 出来的资源 URL、远程 http(s)、data:、blob:。


2. 图层开关

options 支持响应式变更(内部 deep watch 同步显隐),配合内置的 MapToolbar 可以直接做出开关工具栏:

<template>
  <div class="map-page">
    <ThreeMap :options="options" />
    <MapToolbar v-model="options" />
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { ThreeMap, MapToolbar } from "threesmap";
import type { ThreeMapOptions } from "threesmap";
import "threesmap/style.css";

const options = ref<ThreeMapOptions>({
  cloud: true,
  boundary: true,
  rotation: true,
  bar: false,
  heat: false,
  drillDown: true,
});
</script>
字段 默认 说明
cloud true 云层
boundary true 行政区划边界流光
rotation true 底部旋转光环与网格
bar false 数据柱(高度取 mapHoverData 的 population)
heat false 热力图
enterprise true 企业打点。两层共用这一个开关:顶层读 enterpriseData,下钻后读 subEnterpriseData
tooltip true 悬浮信息卡(区域卡 / 企业卡)。关闭只是不弹卡片,高亮、标点、点击聚焦、簇列表都照常
flyLine true 迁徙飞线(仅顶层)。不传 flyLineData 时自然为空
drillDown true 是否允许下钻。关掉后点击区域仍抛 city-click,但不切换层级,便于业务自己接管

所有图层都在初始化时一次性构建,开关只切 visible,所以切换是即时的、没有任何加载。 代价是:想让热力图后续能开关,必须在初始化时就传入 heatmapData;传 null 表示完全不创建。


3. 下钻

点击某个市即进入该市的下钻态:场景里只保留该市的区县,其余市级要素被移除。

下钻态的点击不再切层级:点区县只切换本地高亮(再点同一区县取消),并抛出 @sub-city-click(见下方事件表);点地图外空白处会把视角飞回当前层的默认位置。

需要什么数据

Prop 说明
mapData 顶层(省)GeoJSON
subMapData 全量下级(区县)GeoJSON。下钻时按 parent.adcode 过滤出当前市的子集

不传任何数据时,组件用内置的安徽示例数据直接渲染(零配置可用)。一旦传了自己的 mapData,内置示例业务数据(下级区县 / 城市数据 / 热力图)全部不再兜底——省份对不上,需要哪份就显式传哪份;换省份 = 成对替换 mapData 与 subMapData(或替换 assets/ 里的两个 JSON),组件内部没有任何省份/城市字面量。

配置

<ThreeMap
  :map-data="myMapData"
  :sub-map-data="mySubMapData"
  :drill-down-config="drillConfig"
  @city-click="onCityClick"
  @sub-city-click="onSubCityClick"
  @level-change="onLevelChange"
/>
import type { DrillDownConfig } from "threesmap";

const drillConfig: DrillDownConfig = {
  topLevelName: "安徽省",              // 面包屑里的顶层名称,默认「全省」
  cameraDuration: 900,                // 镜头过渡时长 ms,默认 800
};
字段 类型 默认 说明
topLevelName string "全省" 面包屑顶层名。GeoJSON 没有"省"这一级要素,只能由配置提供
resolveChildren (city, areas) => areas 比较 parent.adcode 自定义"谁是谁的下级"
cameraDuration number 800 镜头过渡时长(ms)
cluster boolean true 企业是否聚合,见第 5 节
clusterRadius number 44 聚合阈值(屏幕像素)
focusDistance number 按层级自适应 点击企业后镜头拉近到的距离
districtFields FieldLabels —— 区县卡片字段,见第 4 节
enterpriseFields FieldLabels —— 企业卡片字段,见第 5 节

事件

事件 参数 触发时机
@city-click { adcode, name } 点击顶层区域。下钻失败(无下级数据)时同样会抛
@sub-city-click { adcode, name, city, selected } 下钻态点击下级区域。不切层级、只切高亮,抛在切换之后,所以 selected 就是点击后的最终态(再点同一区县为 false)
@level-change { level, city } 层级变化。level 为 "province" / "city"

@level-change 常用于让业务自己的覆盖层跟着层级显隐——比如图层工具栏只该在省级显示:

<ThreeMap :options="options" @level-change="onLevelChange" />
<MapToolbar v-if="levelState.level === 'province'" v-model="options" />
import { ref } from "vue";
import type { LevelState } from "threesmap";

const levelState = ref<LevelState>({ level: "province", city: null });
const onLevelChange = (state: LevelState) => (levelState.value = state);

用外部 UI 驱动层级

组件实例暴露了一组方法,配合 ref 即可用任意自己的按钮/菜单控制层级:

<template>
  <ThreeMap ref="mapRef" :sub-map-data="mySubMapData" />
  <button @click="mapRef?.drillDown(340100)">进入合肥市</button>
  <button @click="mapRef?.drillUp()">返回</button>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { ThreeMap } from "threesmap";

const mapRef = ref<InstanceType<typeof ThreeMap> | null>(null);
</script>
方法 签名 说明
drillDown (adcode: number) => boolean 进入指定区域。没有下级数据时返回 false 且不切换层级
drillUp () => void 返回上一层
getLevel () => LevelState 读取当前层级
setSubMapData (data: CityGeoJSON) => void 回填下级 GeoJSON(数据大时先挂载、就绪后回填)
setFlyLineData (data: FlyLineData | null) => void 运行时替换迁徙飞线数据
setHeatmapData (data: HeatmapData | null) => void 运行时替换热力数据

动态更新

哪些数据能"改了就生效"、哪些必须重新挂载组件,一览:

数据 更新方式
flyLineData 改 prop(组件已 watch)或调 setFlyLineData()
flyLineColors 改 prop(会重建飞线层,用新配色)
heatmapData 改 prop(组件已 watch)或调 setHeatmapData()
subMapData 调 setSubMapData();也可不传,等异步加载完再回填
options(图层开关) 改 prop,即时生效(只切显隐,不重建对象)
mapData / 悬浮卡 / 企业数据 需重新挂载组件(参与几何与图层构建)

4. 下级区域信息

下钻后鼠标悬浮区县会弹出信息卡,内容由 subMapHoverData 驱动,key 是 adcode:

<ThreeMap
  :sub-map-data="mySubMapData"
  :sub-map-hover-data="subMapHoverData"
  :drill-down-config="{ districtFields: { population: '人口(万)', enterprises: '企业数' } }"
/>
const subMapHoverData = {
  340102: { population: 146, enterprises: 46 },   // 瑶海区
  340103: { population: 102, enterprises: 38 },   // 庐阳区
};

// districtFields 的对象插入顺序 = 卡片里的渲染顺序
// 这里只显示 population 与 enterprises,其余字段忽略
  • subMapHoverData 的值是自由字段对象,想展示哪些由 districtFields 决定
  • 某区域没有配数据时,信息卡只显示名称(不会报错)

5. 企业布点

企业数据以 adcode 为 key,只有在对应要素可见时才渲染。两层各有一套数据:

Prop 生效层级 key
enterpriseData 顶层(如省视图下的各市) 顶层要素 adcode
subEnterpriseData 下钻后(如市视图下的各区县) 下级要素 adcode

两者共用一个开关 options.enterprise(默认开启),关闭时标点、标签、悬浮卡与簇列表一并隐藏。

<ThreeMap
  :sub-map-data="mySubMapData"
  :enterprise-data="enterpriseData"
  :drill-down-config="{
    enterpriseFields: { industry: '行业', scale: '规模', employees: '员工数' },
    cluster: true,
    clusterRadius: 46,
  }"
  @enterprise-click="onEnterpriseClick"
/>
import type { Enterprise } from "threesmap";

const enterpriseData: Record<string, Enterprise[]> = {
  340102: [
    { id: 1, name: "某电子信息公司", lng: 104.115, lat: 30.601,
      industry: "电子信息", scale: "大型", employees: 860 },
  ],
};

字段要求

字段 必需 说明
id ✅ 唯一标识
name ✅ 标点常显的名称
lng / lat ✅ 经纬度
其它 —— 任意扩展字段,用 enterpriseFields 决定卡片展示哪些
  • 缺必需字段或经纬度非法的记录会被跳过并告警,不会中断整层渲染
  • 落在当前市范围外的企业会被裁掉,避免脏点飘在屏幕外
  • key 必须是 adcode,不是区县名(区县名会跨市重名)。写错时开发期会输出警告

交互行为

操作 行为
悬浮标点 显示信息卡(字段由 enterpriseFields 决定顺序)
点击标点 抛出 enterprise-click,同时镜头只推近、不改变观察角度
悬浮聚合簇 显示该簇包含的企业数量
点击聚合簇 镜头拉近以拆开该簇;已到最近距离仍拆不开时,弹出企业列表浮层

聚合

  • 判据是屏幕像素距离(默认 44px),所以镜头拉近后标点会自然散开,不需要"展开"动画
  • 相机下探到该层最近距离后,成员数 ≤ 3 的小簇会直接展开为独立标点——避免出现"只剩两三家却怎么也拆不开、看不到是谁"的死角
  • cluster: false 可整体关闭聚合,所有企业都渲染为独立标点

点击事件

import type { Enterprise, EnterpriseClickContext } from "threesmap";

const onEnterpriseClick = (enterprise: Enterprise, ctx: EnterpriseClickContext) => {
  console.log(enterprise.name, ctx.district, ctx.city);
  // ctx = { district: "瑶海区", city: { adcode: 340100, name: "合肥市" } }
};

6. 迁徙飞线

只传一个数组就能出效果,端点是 { name, lng, lat }:

<ThreeMap :fly-line-data="flyLineData" />
const flyLineData = [
  { from: { name: "芜湖市", lng: 118.38, lat: 31.33 },
    to:   { name: "合肥市", lng: 117.28, lat: 31.86 }, value: 1280 },
  // ...再放几条
];

效果构成:抛物线拱起的管状弧线(拱高随起终点距离变化)+ 沿线循环前进的流动亮带(各线相位错开)+ 起点呼吸圆环 / 终点光点。鼠标悬浮到线上会提亮该线、压低其余线,并弹出提示。

配色可配(5 个色位,只传要改的):

<ThreeMap
  :fly-line-data="flyLineData"
  :fly-line-colors="{ track: '#7dd3fc', flow: '#0ea5e9', core: '#38bdf8' }"
/>

取色规律:浅色底图上"更亮"表达不出强度,只能用"更饱和",所以默认是浅青轨道 + 饱和青流光。 如果底图换成深色,整组改霓虹色(如 track: '#0e7490', flow: '#22d3ee')会更好看。

提示的两种扩展方式:

// 1) 传函数,完全接管内容
const flyLineTip = (item) =>
  `<b>${item.from.name} → ${item.to.name}</b><br/>运量 ${item.value} 人次`;
/* 2) 不改组件,直接覆盖内置提示元素的样式 */
.tm-fly-tip { border-radius: 4px; }

内置提示元素带 tm-fly-tip class 与 data-from / data-to / data-value 属性,方便外部取用。

飞线支持运行时替换:改 flyLineData prop 或调 setFlyLineData(),组件会重建该图层; 改 flyLineColors 也会用新配色重建。飞线只在顶层出现,下钻态自动隐藏。


API 速查

Props

Prop 类型 默认值 说明
mapData CityGeoJSON 内置安徽省数据 顶层 GeoJSON
subMapData CityGeoJSON 内置(动态加载) 全量区县 GeoJSON,下钻用
mapHoverData Record<string, CityInfo> 内置(仅未传 mapData 时) 顶层悬浮卡数据,key 为顶层要素 adcode;同时决定数据柱高度
subMapHoverData SubMapHoverData —— 下级悬浮卡数据(下钻后),key 为 adcode
enterpriseData EnterpriseData —— 顶层企业布点,key 为顶层要素 adcode
subEnterpriseData EnterpriseData —— 下钻后企业布点,key 为下级要素 adcode
flyLineData FlyLineData —— 迁徙飞线:[{ from, to, value? }],端点是 { name, lng, lat }
flyLineTip FlyLineTipRenderer —— 自定义飞线悬浮提示:(item, index) => string | HTMLElement | null
flyLineColors FlyLineColors 内置青蓝 飞线配色:{ track, flow, core, ring, dot },只传要改的色位
drillDownConfig DrillDownConfig —— 下钻行为统一配置口
heatmapData HeatmapData | null 内置(仅未传 mapData 时) 传 null 表示不创建热力图
textures ThreeMapTextures 内置 8 张(normalMap 为空) 可只覆盖部分 key
options ThreeMapOptions 见第 2 节 图层与交互开关
depth number 6 地图挤出厚度(各层级按跨度等比缩放)
cameraPosition [number, number, number] [-50, 125, 250] 相机初始位置(入场动画的起点)
cameraView CameraView 内置默认 相机视角:决定镜头从哪个方向看地图,所有层级生效
background string #fff5e8 背景色

cameraView 用方位角 / 仰角描述镜头方向,比 xyz 直观:

字段 默认 说明
azimuth 20.6 方位角(度):0 = 从正前方看,90 = 从右侧看
elevation 36.2 仰角(度):0 = 平视,90 = 正上方俯视
distance 1 距离倍数:相对默认视角距离(0.8 = 拉近 20%)。与 scale 二选一
scale 1 放大倍数:2 = 放大两倍(≡ distance: 0.5),比 distance 直观;两者同时传以 scale 为准
<!-- 下面这组就是内置默认视角(不传 cameraView 时即为此);要调就改这几个数 -->
<ThreeMap :camera-view="{ azimuth: 20.6, elevation: 36.2, scale: 1 }" />

对所有层级生效(入场画面 / 下钻与返回 / 点空白复位 / 点聚合簇推近),所以切换层级时视角保持一致; 距离部分仍按各层范围自适应,scale / distance 只是统一的倍数。 不传则完全沿用内置默认视角,行为不变。

Events

事件 参数
@play-complete 无(相机推进 + 图层淡入 + 整体缩放结束)
@city-click { adcode, name }
@sub-city-click { adcode, name, city, selected }
@enterprise-click (enterprise, { district, city })
@level-change { level, city }

实例方法(defineExpose)

方法 说明
drillDown(adcode) 进入指定市,无下级数据时返回 false
drillUp() 返回省级
getLevel() 读取当前 LevelState
setSubMapData(data) 回填下级 GeoJSON(数据大时先挂载、就绪后回填)
setFlyLineData(data | null) 运行时替换迁徙飞线数据
setHeatmapData(data | null) 运行时替换热力数据

完整签名与用法见 用外部 UI 驱动层级。 组件卸载时会自动释放全部资源(含 WebGL 上下文);dispose() 之后这些方法都会直接返回。

包的其它导出

除组件外,threesmap 还导出了内置数据与底层工具,便于按需复用:

import {
  // 内置数据(可用于兜底或参考格式)
  defaultMapHoverData, defaultHeatmapData,
  defaultTextureKeys,        // 内置贴图的 key 清单(8 个,不含 normalMap)
  defaultOptions,
  loadDefaultTextures,       // 按需加载内置贴图地址,返回 Promise(每个 key 一个懒加载分包)
  loadDefaultMapData,        // 异步加载内置省级 GeoJSON,返回 Promise(独立分包)
  loadDefaultSubMapData,     // 异步加载内置区县数据,返回 Promise(独立分包)
  // 底层工具
  loadTexture, textureLoaders, createThreeMap,
  buildLevelData, computeLevelMetrics, indexByParent,
  projectFeatureBounds, defaultResolveChildren,
} from "threesmap";

完整清单见 src/ThreeMap/index.ts。createThreeMap 是纯 three.js 的场景工厂,脱离 Vue 也能用。


注意事项

  1. 父容器必须有明确高度,否则画布高度为 0(组件内部是 100% × 100%)。
  2. 组件在挂载后异步批量加载贴图,全部就绪才创建场景。单张贴图失败(404 / 无 CORS / 超时 15s)只会丢掉那一张,不影响其余贴图与地图渲染。
  3. 下钻态会强制打开边界流光、关闭云层 / 数据柱 / 热力图(那套光效是按省级尺度搭建的,拉近后只剩几道巨大弧线,很突兀);返回时按你的开关原样还原。
  4. 体积:运行时入口约 119 KB(已含内置热力图引擎);8 张内置贴图(约 1.4 MB) 与两级 GeoJSON 都是按需下载——自带地图与贴图的项目运行时不请求这些示例资源 (见上文「产物结构」)。想连这些资产都不装,用精简入口 threesmap/core(约 104 KB)。
  5. 内置的 8 张贴图是示例资源,商用请替换成你自己的图片。 内置贴图以 data: URL 形式加载,若站点配置了 CSP,需保证 img-src 允许 data:; 自己传 URL 的贴图不受此影响。
  6. 组件卸载时会自动释放贴图、几何体、材质、渲染器、WebGL 上下文、控制器监听与注入的 DOM。 GL 上下文不会被残留(浏览器对同时存活的上下文数量有上限,反复挂载 / 卸载也不会耗尽)。
  7. 开销只在需要时发生:CSS2D 标注层是按需刷新的——相机静止、且标注没有增删或显隐变化时, 一整帧不写一次标注 DOM;悬停判定与渲染帧对齐(pointermove 只记最新坐标,每帧最多判定一次)。 实测相机静止 5 秒的样式重算次数为 0(旧行为是每帧全量刷新,约 293 次)。
  8. 畸形数据不会白屏、也不会抛异常:坐标缺失 / 非法、centroid 与 center 双缺、 coordinates 为空、甚至 mapData 是空数组——都只会跳过对应内容并在控制台告警, 其余部分照常渲染。
  9. 直接用底层 createThreeMap(脱离 Vue)时,用完必须调 dispose(): 它会释放 GL 上下文与控制器监听。dispose() 之后该实例的所有方法都会直接返回, 不会再创建图层或重新挂载数据。

控制台告警速查

组件只在开发期告警、从不抛异常中断渲染(前缀统一为 [ThreeMap])。常见几条:

告警 含义 处理
… 的 key 应该是 adcode(如 "340100"),但检测到名称 key 业务数据把区域名当 key 用了 改成对应要素的 properties.adcode
… 里没有任何 key 命中当前层的 adcode key 与当前层级对不上 确认数据传对了层级(顶层 / 下级)
该层级没有任何可投影的有效坐标…已放弃挂载该层 GeoJSON 坐标全缺失或非法 检查 geometry.coordinates
缺少下级数据,无法下钻 没传 subMapData 传 subMapData,或用 setSubMapData 回填
贴图加载失败:<key> → <url> 单张贴图 404 / 未开 CORS / 超时 15s 该张贴图退化为无贴图,其余不受影响
内置贴图分包加载失败:<key> 贴图数据分包缺失 / 网络失败 该张贴图退化为无贴图,其余不受影响
热力图图层创建失败,已跳过该图层 画布不可用或数据异常 只丢热力图,其他图层与开关不受影响
首屏挂载失败,场景为空 数据畸形让图层创建抛错 看告警后的错误详情定位数据

不想翻文档?让 AI 读它

仓库根目录的 CLAUDE.md 是面向 AI 助手的项目入口:包含技术栈与约束、 目录地图、常用命令、开发流程(OpenSpec 变更工作流)、编码与测试规范、发布流程, 以及一张"易踩坑清单"。把仓库丢给 AI 时,让它先读这个文件即可,省去逐篇翻 README。

Related Packages

@tresjs/core

Declarative ThreeJS using Vue Components

1,355,627 3,774
@tresjs/cientos

Collection of useful helpers and fully functional, ready-made abstractions for T...

993,536 3,774
@tresjs/post-processing

Post-processing library for TresJS

386,088 3,771
vue-gl

Vue.js components rendering 3D graphics reactively via three.js

8,863 682
@tresjs/rapier

TresJs physics support, powered by rapier

2,066 3,772

Version History

Version Released Vue License
0.4.0 ^3.3.0 MIT
0.3.0 ^3.3.0 MIT
0.2.0 ^3.3.0 MIT
0.1.1 ^3.3.0 MIT
0.1.0 ^3.3.0 MIT