经纬志
首页 / deck.gl / 教程

自定义图层

先看结论

内置图层覆盖了大多数场景,但「点位带光晕和标注」这类固定组合, 每次都在外部拼装既重复又难复用。自定义图层的价值在于封装,而不是重写着色器。

最后验证 2026-11-03 · deck.gl 9.0 · 难度:进阶 · 核验 2026-10-29 · 引用本文

两条路:组合与底层

基类做法代价适用
CompositeLayer在 renderLayers 里返回若干内置图层几乎没有固定组合、参数化封装
Layer自己管理 Model、着色器、缓冲高,需要 WebGL 知识独有几何、特殊绘制

绝大多数业务需求属于第一类。组合图层不需要写着色器,也能完整继承拾取、 过渡动画和子图层属性。

用 CompositeLayer 封装一组图层

renderLayers() 在每次 setProps 后调用, 返回的图层数组交给 deck.gl 统一调度。getSubLayerProps 负责把外层属性(透明度、可见性、拾取开关)传给子图层。

class StationLayer extends deck.CompositeLayer {
  static layerName = 'StationLayer';

  renderLayers() {
    const data = this.props.data;
    const selectedId = this.props.selectedId;

    return [
      new deck.ScatterplotLayer(this.getSubLayerProps({
        id: 'halo',
        data: data,
        getPosition: function (d) { return [d.lng, d.lat]; },
        getRadius: function (d) { return d.id === selectedId ? 18 : 12; },
        getFillColor: [40, 140, 255, 60],
        radiusUnits: 'pixels'
      })),
      new deck.ScatterplotLayer(this.getSubLayerProps({
        id: 'core',
        data: data,
        getPosition: function (d) { return [d.lng, d.lat]; },
        getRadius: 5,
        getFillColor: [40, 140, 255, 230],
        radiusUnits: 'pixels'
      })),
      new deck.TextLayer(this.getSubLayerProps({
        id: 'label',
        data: data,
        getPosition: function (d) { return [d.lng, d.lat]; },
        getText: function (d) { return d.name; },
        getSize: 12,
        getColor: [22, 24, 29, 255],
        getTextAnchor: 'middle',
        getAlignmentBaseline: 'bottom',
        getPixelOffset: [0, -14],
        sizeUnits: 'pixels'
      }))
    ];
  }
}

var layer = new StationLayer({
  id: 'stations',
  data: stations,
  selectedId: 'BJ-001',
  pickable: true
});

三个子图层共享同一个 data,位置访问器重复写了三次。 数据量很大时可以预先把坐标存成 d.position,访问器直接返回它, 避免每次求值都新建数组。

子图层 id 要稳定且唯一。getSubLayerProps 会拼接外层 id, 但同一组合图层内两个子图层不能同名,否则后者会被当成前者复用。

底层 Layer 的四个钩子

真要自己写着色器时,继承 deck.Layer,实现四个方法。 骨架如下,真正的绘制逻辑放在 draw 里,用 Model 绑定顶点缓冲与 uniform。

class HeatGridLayer extends deck.Layer {
  static layerName = 'HeatGridLayer';
  static defaultProps = { cellSize: 200 };

  initializeState() {
    // 创建 Model、缓冲、纹理,只在图层首次挂载时执行
    this.state.model = this.createModel();
  }

  updateState(params) {
    super.updateState(params);
    const { props, oldProps, changeFlags } = params;

    if (changeFlags.dataChanged || props.cellSize !== oldProps.cellSize) {
      this.state.model.setAttributes({
        aGrid: this.buildGrid(props.data, props.cellSize)
      });
    }
  }

  draw({ uniforms }) {
    this.state.model.setUniforms(uniforms).draw();
  }

  getPickingInfo({ info }) {
    // 把拾取到的索引映射回数据项
    info.object = info.index >= 0 ? this.props.data[info.index] : null;
    return info;
  }

  createModel() { /* 返回 new Model(...) */ }
  buildGrid(data, size) { /* 返回计算好的缓冲数据 */ }
}

initializeState 只执行一次,updateState 在属性变化时执行,draw 每帧执行。把数据计算放在 updateState,不要放在 draw,否则每帧都会重复计算。

注意事项

  • 组合图层的拾取默认由子图层承担,外层 onClick 需要显式传给子图层。
  • renderLayers 里不要创建新的数据数组,否则每次渲染都会触发完整重建。
  • 自定义 Layer 的 defaultProps 决定了哪些属性变化会触发 updateState。
  • 直接操作 WebGL 资源时,要在 finalizeState 里销毁 Model,否则切换页面后显存不释放。
  • 组合图层的 transitions 不能直接写在子图层上,需要通过 props 透传。
引用本文

经纬志(2026)。《自定义图层》。https://www.jingweizhi.com/deckgl/tutorial/custom-layer.html(访问日期 2026-11-03)。