自定义图层
内置图层覆盖了大多数场景,但「点位带光晕和标注」这类固定组合, 每次都在外部拼装既重复又难复用。自定义图层的价值在于封装,而不是重写着色器。
两条路:组合与底层
| 基类 | 做法 | 代价 | 适用 |
|---|---|---|---|
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)。