经纬志
首页 / OpenLayers / 教程

性能优化:从几千个要素到十万级

先看结论

OpenLayers 的矢量图层默认走 Canvas 2D,每个要素都要调用绘图指令。 数据量上去之后,换渲染后端比调样式参数有效得多。

最后验证 2026-11-03 · OpenLayers 10.3.1 · 难度:进阶 · 核验 2026-10-29 · 引用本文

先判断瓶颈在哪一档

要素规模推荐方案原因
数百个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)。