性能优化:从几千个要素到十万级
OpenLayers 的矢量图层默认走 Canvas 2D,每个要素都要调用绘图指令。 数据量上去之后,换渲染后端比调样式参数有效得多。
先判断瓶颈在哪一档
| 要素规模 | 推荐方案 | 原因 |
|---|---|---|
| 数百个 | ol.layer.Vector 默认样式 | 无需优化,交互与样式最灵活 |
| 几千个 | 预建样式对象、关闭不需要的声明式避让 | 样式函数调用次数成为主要开销 |
| 一万到十万 | ol.layer.WebGLPoints | 批量上传到 GPU,一次绘制 |
| 十万以上 | 聚合、瓦片化或服务端切片 | 浏览器端已不适合逐条渲染 |
WebGL 点图层
ol.layer.WebGLPoints 用一套声明式样式描述代替样式函数,
样式被编译成着色器,所有点在一个批次里画完。代价是不支持任意 Canvas 绘制,
只支持圆、图标、形状等有限表达。
var pointsLayer = new ol.layer.WebGLPoints({
source: new ol.source.Vector({
url: '/data/stations.geojson',
format: new ol.format.GeoJSON()
}),
style: {
'circle-radius': [
'interpolate', ['linear'], ['get', 'flow'],
0, 3,
500, 8,
2000, 14
],
'circle-fill-color': [
'match', ['get', 'level'],
'high', '#d0453a',
'mid', '#f0a020',
'#1f5fd0'
],
'circle-opacity': 0.82,
'circle-stroke-width': 1,
'circle-stroke-color': '#ffffff'
}
});
map.addLayer(pointsLayer);
样式表达式里的 ['get', 'flow'] 读取要素属性。
属性值必须是数值或字符串,对象和数组无法直接参与表达式运算。
坐标系要一致。WebGL 点图层在渲染时使用视图投影,
数据源的 dataProjection 必须正确声明,
否则点会整体偏移,且不会有任何报错。
减少无效重绘
默认配置下,拖动和缩放过程中矢量图层会持续重绘。 对静态数据,可以把重绘推迟到交互结束:
var parcels = new ol.layer.Vector({
source: parcelSource,
updateWhileAnimating: false,
updateWhileInteracting: false,
declutter: false,
style: cachedStyle
});
// 渲染缓冲:多画一圈视野外的内容,拖动时更少出现白边
var map = new ol.Map({
target: 'map',
layers: [base, parcels],
renderBuffer: 200,
view: view
});
updateWhileInteracting: false 让图层在拖动时保持上一帧内容,
松手后再重绘。地图看起来会更「稳」,代价是拖动中数据不更新。
样式函数里只做查表
样式函数在每次重绘时对每个要素调用。在函数里新建
ol.style.Style 会产生大量对象,触发频繁 GC。
按分类预建样式,函数里只做一次字典查找:
var styleCache = {};
function styleFor(level) {
if (!styleCache[level]) {
styleCache[level] = new ol.style.Style({
image: new ol.style.Circle({
radius: level === 'high' ? 7 : 5,
fill: new ol.style.Fill({ color: level === 'high' ? '#d0453a' : '#1f5fd0' }),
stroke: new ol.style.Stroke({ color: '#fff', width: 1.5 })
})
});
}
return styleCache[level];
}
var layer = new ol.layer.Vector({
source: source,
style: function (feature) {
return styleFor(feature.get('level'));
}
});
数据侧的三件事
- 裁剪几何:用
ol.geom.Geometry.simplify或服务端预简化,减少顶点数。 - 限制请求范围:给数据源配置
strategy: ol.loadingstrategy.bbox,按视野分块加载。 - 提早聚合:低缩放级别用
ol.source.Cluster,高缩放级别再展开原始点。
var clustered = new ol.source.Cluster({
distance: 48,
minZoom: 12,
source: new ol.source.Vector({
url: '/data/stations.geojson',
format: new ol.format.GeoJSON(),
strategy: ol.loadingstrategy.bbox
})
});
var layer = new ol.layer.Vector({
source: clustered,
style: function (feature) {
var size = feature.get('features').length;
return new ol.style.Style({
image: new ol.style.Circle({
radius: 8 + Math.min(size, 50) * 0.4,
fill: new ol.style.Fill({ color: 'rgba(31, 95, 208, 0.85)' }),
stroke: new ol.style.Stroke({ color: '#fff', width: 2 })
}),
text: new ol.style.Text({
text: String(size),
font: '600 11px sans-serif',
fill: new ol.style.Fill({ color: '#fff' })
})
});
}
});
注意事项
declutter: true能避免标注压盖,但每帧要做碰撞检测,标注上千时开销明显。- WebGL 图层无法使用
ol.style.Style对象,混用会直接不渲染。 renderBuffer越大,内存与瓦片请求越多,200 像素左右是常见折中。- 不要在
postrender里修改图层数据,会触发无限重绘。 - 移动端像素比高,画布实际像素是尺寸的两到三倍,填充率开销要按物理像素估算。
经纬志(2026)。《性能优化:从几千个要素到十万级》。https://www.jingweizhi.com/openlayers/tutorial/performance.html(访问日期 2026-11-03)。