图标与字体
符号图层画两种东西:图标与文字。图标来自雪碧图(sprite)或运行时添加的图片, 文字来自字体切片(glyphs)。两者都是二进制资源,配错时图层往往静默为空。
sprite 与 glyphs 的位置
样式顶层的 sprite 指向一个 JSON 索引,索引里记录了每张图标在
雪碧图里的位置与尺寸;glyphs 是 PBF 字体的地址模板,
MapLibre 会按 {fontstack}/{range} 逐段取字。
{
"version": 8,
"sprite": "https://example.com/styles/basic/sprite",
"glyphs": "https://example.com/fonts/{fontstack}/{range}.pbf",
"sources": { /* ... */ },
"layers": [
{
"id": "poi-label",
"type": "symbol",
"source": "poi",
"layout": {
"icon-image": ["get", "kind"],
"icon-size": 0.8,
"text-field": ["get", "name"],
"text-font": ["Noto Sans Regular"],
"text-size": 12,
"text-offset": [0, 1.2]
}
}
]
}
sprite 不写扩展名,MapLibre 会自动拼出 .json 与
.png(或 @2x 版本)。手写 .json 后缀会导致
请求路径变成 sprite.json.json。
运行时添加图片
图标不在雪碧图里时,用 map.addImage() 直接塞进去。
图片可以是 ImageData、HTMLImageElement,
也可以是 {width, height, data} 形式的原始像素。
function makeDot(color) {
var size = 48;
var canvas = document.createElement('canvas');
canvas.width = canvas.height = size;
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2 - 4, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
ctx.lineWidth = 4;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
return ctx.getImageData(0, 0, size, size);
}
map.on('load', function () {
for (var i = 0; i < 5; i++) {
map.addImage('dot-' + i, makeDot('#17694a'), { pixelRatio: 2 });
}
map.addLayer({
id: 'dots',
type: 'symbol',
source: 'sites',
layout: {
'icon-image': ['concat', 'dot-', ['to-string', ['get', 'level']]],
'icon-size': 0.5,
'icon-allow-overlap': true
}
});
});
pixelRatio: 2 表示图片是 2 倍图。设了之后,
icon-size: 0.5 的实际显示尺寸等于设计稿尺寸;
不设 pixelRatio 时图片会按原始像素渲染,在高分屏上显得偏大。
可染色的 SDF 图标
普通图片的颜色被锁死在像素里,改 icon-color 没有效果。
用 sdf: true 添加的图标保存的是距离场,可以被
icon-color 与 icon-halo-color 染色。
map.addImage('pin-sdf', imageData, { sdf: true, pixelRatio: 2 });
map.addLayer({
id: 'pins',
type: 'symbol',
source: 'sites',
layout: { 'icon-image': 'pin-sdf', 'icon-size': 0.6 },
paint: {
'icon-color': ['match', ['get', 'status'],
'ok', '#17694a',
'warn', '#c2410c',
'#6b7280'
],
'icon-halo-color': '#ffffff',
'icon-halo-width': 1
}
});
文字的四个关键属性
| 属性 | 作用 | 常见错误 |
|---|---|---|
text-field | 文字内容,通常是 ['get', 'name'] | 属性为数字,未用 to-string |
text-font | 字体栈,必须与 glyphs 服务一致 | 写成 CSS 字体名,文字全空 |
text-size | 字号,可随缩放插值 | 单位写成 px,实际是无单位数值 |
text-halo-width | 描边宽度,压在深色底图上必需 | 大于 2 后文字糊成一团 |
text-font 是数组,表示字体栈。服务里存在
Noto Sans Regular 这一组,就直接写这个名字;
写 Noto Sans(缺少字重)会取不到字体。
避让与重叠
符号图层默认参与碰撞检测:文字互相避让,被挤掉的要素不绘制。
调试时把 text-allow-overlap 打开,能立刻看出是数据没进来,
还是被避让逻辑吃掉了。
map.addLayer({
id: 'station-labels',
type: 'symbol',
source: 'stations',
minzoom: 12,
layout: {
'text-field': ['get', 'name'],
'text-font': ['Noto Sans Regular'],
'text-size': ['interpolate', ['linear'], ['zoom'], 12, 11, 18, 15],
'text-padding': 4,
'text-max-width': 8,
'text-allow-overlap': false,
'text-ignore-placement': false
},
paint: {
'text-color': '#12202c',
'text-halo-color': 'rgba(255,255,255,0.9)',
'text-halo-width': 1.5
}
});
注意事项
addImage 的图片在换样式后消失
addImage 只作用于当前地图实例的图片表,不写进样式 JSON。
setStyle 之后需要重新 addImage,
否则 icon-image 找不到图片,整个符号图层不绘制。
图标名大小写与拼写
雪碧图里的图标名区分大小写。用 ['get', 'icon'] 直接把数据字段
当图标名,数据里一个拼写不一致的图标就静默缺失。上线前跑一遍
map.hasImage(name) 校验。
字体切片缺失中文
多数公共字体服务只提供拉丁字符。中文注记要么自建 glyphs(用
fontnik 切分 Noto Sans SC),要么改用 HTML 标记(Marker)承载文字。
前者体积大,后者会失去随地图旋转的能力。
经纬志(2026)。《图标与字体》。https://www.jingweizhi.com/maplibre/tutorial/sprite-glyphs.html(访问日期 2026-11-03)。