经纬志
首页 / OpenLayers / 教程

图层叠加与顺序

先看结论

地图上的压盖关系由图层顺序决定,但顺序有两个来源:数组位置和 zIndex。 两者同时存在时,zIndex 说了算,这也是「明明把图层放到最后却还是被盖住」的原因。

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

图层集合就是一个数组

map.getLayers() 返回 ol.Collection, getArray() 拿到真实数组。数组前面的图层先绘制, 后面的盖在上面。

var base = new ol.layer.Tile({
  source: new ol.source.OSM(),
  zIndex: 0
});

var parcels = new ol.layer.Vector({
  source: new ol.source.Vector({ url: '/data/parcels.geojson', format: new ol.format.GeoJSON() }),
  zIndex: 10,
  opacity: 0.85
});

var labels = new ol.layer.Vector({
  source: labelSource,
  zIndex: 20,
  declutter: true
});

var map = new ol.Map({
  target: 'map',
  layers: [base, parcels, labels],
  view: new ol.View({
    center: ol.proj.fromLonLat([116.397, 39.908]),
    zoom: 12
  })
});

opacity 作用于整层,包含标注。需要「面半透明、点不透明」时, 把面和点拆成两个图层,各自设透明度。

控制项API作用范围
数组顺序getLayers().insertAt / removeAt整体重排,代价是一次重绘
zIndexlayer.setZIndex(n)数值大的在上,未设置视为 0
可见性layer.setVisible(false)整层隐藏,不参与渲染
透明度layer.setOpacity(0.5)整层混合,含所有子要素
范围layer.setExtent(rect)只渲染指定范围,可裁掉无关瓦片

zIndex 与数组顺序的优先级

OpenLayers 在每次渲染前按 zIndex 排序图层。只要给图层设了不同的 zIndex,数组里的位置就不影响最终结果。 全部为默认值 0 时,才按数组顺序绘制。

// 这两个写法结果不同
map.getLayers().insertAt(0, highlightLayer);  // 数组最前面
highlightLayer.setZIndex(5);                  // 但 zIndex 更大,画在最上面

// 想让高亮真正压在最上层,zIndex 要大于其它图层
map.getLayers().forEach(function (layer) {
  if (layer !== highlightLayer) {
    layer.setZIndex(layer.getZIndex() + 1);
  }
});
highlightLayer.setZIndex(100);

动态调整时优先改 zIndex,不要频繁重排数组。 重排会让渲染器重新计算图层组,图层多时有可感知的卡顿。

用组图层成批管理

ol.layer.Group 把多个图层打包,对组设置可见性和透明度, 效果会传递到组内所有图层。专题切换、底图分组都适合用它。

var business = new ol.layer.Group({
  layers: [parcels, labels],
  zIndex: 10
});

map.addLayer(business);

// 整组隐藏,组内图层自身的 visible 状态保持不变
business.setVisible(false);

// 组内再叠加一个图层,仍然受组控制
business.getLayers().push(waterLayer);

// 组透明度会与子图层透明度相乘
business.setOpacity(0.6);

组透明度会相乘。组设 0.6、子图层设 0.5,最终是 0.3。 排查「太淡了」的问题时,先看组和子图层是不是都设过透明度。

底图切换的常见结构

多个底图不要全部添加再切换可见性,未显示的瓦片图层不会请求数据, 但保留在图层集合里仍会增加渲染调度。更干净的做法是只保留当前底图。

var osm = new ol.layer.Tile({ source: new ol.source.OSM(), zIndex: 0 });
var imagery = new ol.layer.Tile({
  source: new ol.source.XYZ({
    url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
    attributions: '© OpenStreetMap contributors'
  }),
  zIndex: 0
});

function switchBase(next) {
  map.getLayers().getArray().forEach(function (layer) {
    if (layer.get('isBase')) map.removeLayer(layer);
  });
  next.set('isBase', true);
  map.getLayers().insertAt(0, next);
}

switchBase(osm);

注意事项

  • zIndex 只在同一地图内比较,嵌套组图层时组内图层仍按自身 zIndex 排序。
  • 矢量图层与瓦片图层混合时,矢量总是绘制在瓦片之上,除非瓦片被放进更高的组。
  • 频繁 setZIndex 会触发重绘,批量调整后再统一渲染一次更划算。
  • 图层 extent 只是裁剪显示范围,不会减少数据源请求。
  • 地图销毁时要调用 map.setTarget(null),否则图层与事件监听会残留。
引用本文

经纬志(2026)。《图层叠加与顺序》。https://www.jingweizhi.com/openlayers/tutorial/layer-order.html(访问日期 2026-11-03)。