前端怎么把 GeoJSON 画到地图上?Canvas / SVG / 地图库选型
前面三篇把数据和格式讲完了,这一篇聊"最后一公里",也就是怎么让 GeoJSON 在浏览器里真正显示出来。我做过一个全国行政区划边界的项目(GeoJSONcn),省市区县乡镇四级、几万个面,渲染性能踩过的坑够写一本书。这里把最关键的选型逻辑给你捋清楚。
三条路,差别挺大
| 方案 | 原理 | 适合 |
|---|---|---|
| SVG | 每个要素一个 DOM 节点 | 要素少(几十~几百)、要交互(hover/点击) |
| Canvas | 全部画在一张位图里 | 要素多(上千~几万)、只要看 |
| 地图库(Leaflet/ECharts/Mapbox) | 封装好投影+缩放+瓦片 | 要完整地图体验(平移/缩放/底图) |
SVG:简单但撑不住大数据
SVG 的好处是"每个面都是真 DOM",CSS 就能改颜色、加 hover。十几条边界做示意图很爽:
<svg viewBox="0 0 800 600">
<path d="M100 100 L200 120 ... Z" fill="#cde" />
</svg>
但一个 就是一个 DOM 节点,几千个面浏览器就卡成 PPT。所以 SVG 适合展示少量重点区域,不适合全国全量。
Canvas:快,但得自己操心
Canvas 把所有面画在一张位图里,DOM 节点永远是 1 个。几万个面 60fps 没问题。代价是你得自己处理:
- 经纬度到像素投影(还记得上篇的 project 函数吗)
- 命中检测(鼠标移到哪个面?要用 isPointInPath,或自己算射线法)
- 重绘(任何变化都得 clearRect 重画)
function render(geojson, canvas) {
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const f of geojson.features) {
ctx.beginPath();
f.geometry.coordinates[0].forEach((c, i) => {
const [x, y] = project(c[0], c[1]);
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.closePath();
ctx.fill();
}
}
我在 GeoJSONcn 的预览器里就是 Canvas 渲染,几万个乡镇面拖动依然顺滑。
地图库:别重复造轮子
如果你想要"真地图"体验(底图、缩放、平移、标注),直接用库:
- Leaflet:轻量,GeoJSON 图层一行接入,配 OSM/天地图瓦片;
- ECharts:中国地图标配,registerMap 灌入 GeoJSON 就能画热力/飞线;
- Mapbox / 高德 / 百度:商业底图,注意坐标系(前面 WGS84/GCJ-02 那篇)。
Leaflet 接 GeoJSON 就一句话:
L.geoJSON(geojsonData).addTo(map);
一个真实选型建议
我在 GeoJSONcn 里是分层用的:
- 全量边界(几万个面)走 Canvas,保证性能;
- 交互详情(当前 hover 的省市)单独拎出来,或者配合地图库做高亮;
- 用户要更像地图的体验,提供 Leaflet 嵌入示例。
别迷信某一个。小数据 SVG 最省事,大数据 Canvas 最稳,要体验上地图库。
最后
要素少要交互,用 SVG;要素多只要看,用 Canvas;要完整地图体验,上 Leaflet / ECharts / Mapbox。渲染本质就是投影加画,选对工具比写复杂代码重要。下一篇换个角度:这些页面怎么做到几万张还能在国内秒开?这就得聊静态生成和边缘加速了。