首页 / MapLibre / API 文档
StyleImageInterface
先看结论
动态图标的约定接口:给 addImage 传一个带像素数组与 render 方法的对象,图标就能逐帧变化。
interface StyleImageInterface = { width, height, data, render?, onAdd?, onRemove? }
它是实现约定,不是可导入的类。普通对象只要字段齐全就能用。
字段
| 字段 | 说明 |
|---|---|
width | 图像宽度,像素 |
height | 图像高度,像素 |
data | RGBA 顺序的像素数据,长度必须为 width × height × 4;也可传 WebGL 数据回调 |
render(map?) | 每帧在图标被使用前调用,返回 true 表示像素已更新 |
onAdd(map, id) | 图标加入地图时调用,可在这里保存地图引用 |
onRemove() | 图标被移除时调用,用来停掉定时器与清理资源 |
呼吸灯效果
var SIZE = 48;
var pulse = {
width: SIZE,
height: SIZE,
data: new Uint8Array(SIZE * SIZE * 4),
onAdd: function (map) {
this.map = map;
this.start = performance.now();
},
render: function () {
var t = (performance.now() - this.start) / 1000;
var alpha = 0.35 + 0.35 * Math.sin(t * 2.4);
var r = SIZE / 2 - 2;
for (var y = 0; y < SIZE; y++) {
for (var x = 0; x < SIZE; x++) {
var dx = x - SIZE / 2;
var dy = y - SIZE / 2;
var inside = dx * dx + dy * dy <= r * r;
var i = (y * SIZE + x) * 4;
this.data[i] = 31;
this.data[i + 1] = 95;
this.data[i + 2] = 208;
this.data[i + 3] = inside ? Math.round(alpha * 255) : 0;
}
}
this.map.triggerRepaint();
return true;
},
onRemove: function () {
this.map = null;
}
};
map.addImage('pulse', pulse);
data 的每个像素占四个字节,索引按行优先递增。
透明度写第四个字节,圆形之外置 0,否则图标会是一个实心方块。
何时返回 true
render 返回 true 才把新像素提交到图标表。
每帧都返回真会让地图持续重绘,CPU 与显存带宽都被占满;
内容没变时返回 false,动画只在需要时推进。
播放一次就停的动画可以在结束时不再调用 triggerRepaint,
地图随即回到 idle 状态,事件循环有空闲处理其它任务。
在图层里引用
map.addLayer({
id: 'pulse-layer',
type: 'symbol',
source: 'poi',
layout: {
'icon-image': 'pulse',
'icon-size': 0.6,
'icon-allow-overlap': true
}
});
与逐帧 updateImage 的选择
| 方式 | 开销 | 适用 |
|---|---|---|
StyleImageInterface | 每帧一次 CPU 像素写入与上传 | 持续动画、每帧都变 |
map.updateImage | 调用时上传一次 | 低频更新、事件驱动变化 |
render 里不要画 Canvas。
接口要的是像素数组,不是绘制指令。需要 Canvas 绘制能力时,
先在离屏 Canvas 上画好,再 getImageData 拷进 data;
但每帧拷贝成本更高,能直接算像素就别用 Canvas。
经纬志(2026)。《StyleImageInterface》。https://www.jingweizhi.com/maplibre/api/style-image-interface.html(访问日期 2026-11-03)。