首屏加载十几秒,用户等不及就关了
用户打开页面,先看到空白背景几秒,再看到一堆灰块慢慢变成地图,最后才是数据。 每一段都在流失人。
先分清是哪一段慢
不要笼统地说「加载慢」。用网络面板看时间线,通常是下面四段之一占了大头, 优化手段完全不同。
| 阶段 | 表现 | 优化方向 |
|---|---|---|
| 库下载 | 白屏,什么都没有 | 减体积、换 CDN、预加载 |
| 首屏瓦片 | 灰块逐个变清晰 | 降初始层级、限制并发、用低清占位 |
| 业务数据 | 底图有了但点没出来 | 减数据量、按视野请求、分页 |
| 渲染阻塞 | 数据到了但页面卡住不动 | 分批渲染、Web Worker、虚拟化 |
库体积:Cesium 是重灾区
Leaflet 压缩后约 42 KB,OpenLayers 完整包大很多,Cesium 的
Cesium.js 加资源接近 3 MB。三者的优化空间完全不同。
| 库 | 约体积 | 能做的 |
|---|---|---|
| Leaflet | 42 KB | 基本到极限,别再多引插件 |
| OpenLayers | 数百 KB | 按需引入模块,不要整包 |
| Cesium | 约 3 MB | 用官方构建裁剪;首屏前加骨架屏 |
Cesium 首屏慢是物理限制,与其死磕下载时间,不如给用户一个明确的加载进度, 让人知道还要等多久。什么都没显示的空白比转圈更难受。
瓦片:别一上来就三级细节
初始缩放级别越高,首屏需要的瓦片就越多。把初始层级从 15 降到 12, 瓦片数量可能减少到八分之一。
var map = L.map('map', {
center: [39.9, 116.4],
zoom: 11, // 先给一个粗略的层级
minZoom: 3
});
另外可以限制并发请求数。并发太高会把带宽均摊到每张瓦片上,单张反而更慢, 首屏整体可见时间被拖长。
// OpenLayers
var map = new ol.Map({
// ...
maxTilesLoading: 8 // 默认 16,调低可让首屏瓦片更快出齐
});
数据:按视野请求,不要一次拉全量
最常见的错误是页面一加载就请求全国数据。正确做法是先拿到当前视野范围, 只请求这个范围内的数据,移动结束后再增量拉取。
function loadForViewport(map) {
var b = map.getBounds();
fetch('/api/points?bbox=' + [
b.getWest(), b.getSouth(), b.getEast(), b.getNorth()
].join(','))
.then(function (r) { return r.json(); })
.then(render);
}
map.on('moveend', function () { loadForViewport(map); });
loadForViewport(map);
渲染:分批插入
一次性往地图里加两千个要素,主线程会被占住,页面卡住几百毫秒。 分批插入可以让浏览器有机会插进来响应用户操作。
function addInBatches(items, size) {
var i = 0;
function next() {
var end = Math.min(i + size, items.length);
for (; i < end; i++) {
group.addLayer(makeMarker(items[i]));
}
if (i < items.length) {
requestAnimationFrame(next);
}
}
next();
}
优化的优先顺序
- 先量一下:网络面板里到底哪一段占了大头
- 数据全量拉取 → 改按视野请求,收益最大
- 初始层级过高 → 降到 10–12
- 一次性插入大量要素 → 改分批
- 库体积 → 按需引入或裁剪构建
- 最后再考虑骨架屏和加载进度,改善的是感受不是速度
先做能立刻看到的:加一个明确的加载提示层。 用户能接受等三秒,不能接受「不知道还要等多久」。 这一项几乎零成本,但对跳失率的影响往往比优化瓦片更大。
经纬志(2026)。《首屏加载十几秒,用户等不及就关了》。https://www.jingweizhi.com/cookbook/first-screen-slow.html(访问日期 2026-11-03)。