首页 / 实战手册 / 数据加载
20 MB 的 GeoJSON 同步解析,主线程卡了 8 秒
先看结论
文件不大,但打开页面后转圈八秒,期间点击任何按钮都没反应。
现象
一份 20.4 MB、12 万个 Polygon 的行政区划 GeoJSON。
fetch 只用了 600 ms,但 L.geoJSON(data).addTo(map) 之后
页面冻结约 8 秒,Performance 面板显示主线程被一个任务占满,
出现 [Violation] 'click' handler took 8120ms。
加载完成后地图拖动也很卡,帧率从 60 掉到 12。
原因
三个阶段都在主线程:
JSON.parse解析 20 MB 文本,约 300~600 ms。L.geoJSON为每个要素创建L.Path、计算 bounds、绑定事件,12 万次对象创建约 5 秒。- 每帧渲染 12 万个 SVG 路径,帧率必然崩溃。
解析本身不是瓶颈,逐个创建矢量对象才是。 Leaflet 的 SVG 渲染器对超过一万个路径就无法维持 60 帧。
排查过程
- Performance 面板录制加载过程,看长任务(红色三角)落在解析还是渲染阶段。
- 在
fetch之后、L.geoJSON之前打时间戳,分离网络与解析耗时。 - 统计要素数量与顶点总数,顶点数超过 50 万就需要先简化。
- 把要素数从 12 万降到 5000 再测一次,若卡顿消失,就是数量问题。
const t0 = performance.now();
const res = await fetch('/data/districts.geojson');
const text = await res.text();
const t1 = performance.now(); // 网络 + 下载
const fc = JSON.parse(text);
const t2 = performance.now(); // 解析
L.geoJSON(fc).addTo(map);
const t3 = performance.now(); // 建对象
console.log('fetch', t1 - t0, 'parse', t2 - t1, 'render', t3 - t2);
解决方案
三条路按投入从低到高:先简化几何,再按视野裁剪,最后换渲染方式。
// 1. 用 mapshaper 预简化,顶点降到 10% 以内
// npx mapshaper districts.geojson -simplify 10% keep-shapes -o precision=0.0001 out.geojson
// 2. 服务端按 bbox 返回,只取视野内的要素
const b = map.getBounds();
fetch(`/api/districts?bbox=${b.getWest()},${b.getSouth()},${b.getEast()},${b.getNorth()}`);
// 3. 渲染层换成 Canvas,显著减少对象开销
L.geoJSON(fc, { renderer: L.canvas({ padding: 0.5 }) }).addTo(map);
// 4. 只做展示、不需要交互时,直接渲染成图片或矢量瓦片
// Mapbox GL JS + vector tiles 可承载 10 万级要素
必须保留全量数据时,用 requestIdleCallback 分批加入,避免单帧阻塞。
const queue = fc.features.slice();
function step(deadline) {
while (queue.length && deadline.timeRemaining() > 2) {
L.geoJSON(queue.shift()).addTo(layerGroup);
}
if (queue.length) requestIdleCallback(step);
}
requestIdleCallback(step);
验证
优化后 render 阶段应低于 800 ms,首屏可交互时间小于 2 秒;
拖动地图时帧率保持在 50 以上,Performance 面板不再出现超过 50 ms 的长任务。
console.assert(t3 - t2 < 800, '建对象仍超过 800ms');
// 帧率检查:连续拖动 3 秒,统计 requestAnimationFrame 次数
let frames = 0; const start = performance.now();
(function tick() { frames++; if (performance.now() - start < 3000) requestAnimationFrame(tick);
else console.log('fps', frames / 3); })();
适用版本
| 库 / 工具 | 版本 | 说明 |
|---|---|---|
| Leaflet | 1.9.x | SVG 渲染器建议不超过 1 万路径,Canvas 可到 5 万 |
| Mapbox GL JS | 2.15 / 3.x | 矢量瓦片方案可承载十万级要素 |
| mapshaper | 0.6.x | -simplify 支持按百分比或精度 |
经纬志(2026)。《20 MB 的 GeoJSON 同步解析,主线程卡了 8 秒》。https://www.jingweizhi.com/cookbook/geojson-large-freeze.html(访问日期 2026-11-03)。