Marker 与自定义图标
标记是 Leaflet 里唯一用 DOM 绘制的图层,这让它可以放任意 HTML,
也让它在几千个以上时成为性能瓶颈。图标本身是一个 L.Icon 实例,
L.icon 只是它的工厂函数。
默认图标为什么经常不显示
默认图标不写死在 JS 里,而是按一个相对路径去拼图片地址。
一旦把 leaflet.js 从 CDN 引入、把 CSS 放在别处、或者被构建工具改写了目录结构,
拼出来的地址就是 404,画布上只留下一个不可见的点击区域。
Marker 元素仍然会创建,所以控制台不会报错。
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.css">
<script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
// 显式告诉 Leaflet 图标资源目录,避免相对路径拼错
L.Icon.Default.imagePath = 'https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/images/';
var map = L.map('map').setView([39.907359, 116.391263], 12);
</script>
imagePath 只影响默认图标。自定义图标一律写完整 URL,
不受这个设置影响,也不会因为部署目录变化而失效。
L.icon 的六个参数
| 参数 | 含义 | 常见错误 |
|---|---|---|
iconUrl | 图片地址,必填 | 写成相对路径,部署后 404 |
iconSize | 图标的显示尺寸 [宽, 高] | 漏写则按图片原始像素渲染,大图会撑破布局 |
iconAnchor | 图标内哪个点对准坐标 | 漏写默认以左上角对准,视觉上整体偏移半个图标 |
popupAnchor | 弹窗相对锚点的偏移 | 不设会盖住图标本身 |
shadowUrl | 阴影图片 | 2 倍图配 1 倍阴影,比例不对 |
className | 附加到图标元素的类名 | 用它做 CSS 动画时忘了处理 transform |
var pin = L.icon({
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41], // 底部尖端对准坐标
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
L.marker([39.907359, 116.391263], { icon: pin })
.bindPopup('天安门')
.addTo(map);
iconAnchor 是最容易被忽略的参数。标记的坐标语义是「图上这一个点」,
而图片是一个矩形,必须指定矩形内的哪个位置代表那个点。
对图钉类图标,答案通常是底部尖端;对圆形头像,是中心。
divIcon:用 HTML 画图标
需要显示数字、状态、头像或纯 CSS 图形时,用 L.divIcon。
它不加载图片,直接插入一个 div,内容与样式全部自己控制。
function numberIcon(n, color) {
return L.divIcon({
className: 'num-pin',
html: '<span style="background:' + color + '">' + n + '</span>',
iconSize: [26, 26],
iconAnchor: [13, 13]
});
}
L.marker([39.916345, 116.397155], { icon: numberIcon(1, '#c2410c') }).addTo(map);
L.marker([39.899000, 116.407000], { icon: numberIcon(2, '#17694a') }).addTo(map);
divIcon 的内容会当 HTML 插入。数字、颜色这类自己生成的值可以拼字符串, 来自用户输入的文本必须先转义,否则等于在地图里开了一个 XSS 入口。
旋转与交互选项
1.9 的标记支持 rotationAngle 与 rotateOrigin,
用于风向标、车头朝向这类场景。它们作用在图标元素上,不影响坐标计算。
| 选项 | 默认 | 作用 |
|---|---|---|
draggable | false | 允许拖动,配合 dragend 取新坐标 |
keyboard | true | 可用 Tab 聚焦、回车触发点击 |
riseOnHover | false | 悬停时提升到同 pane 最上层 |
zIndexOffset | 0 | 同一 pane 内的叠放微调 |
opacity | 1 | 整体透明度 |
var m = L.marker([39.907359, 116.391263], {
draggable: true,
riseOnHover: true,
rotationAngle: 30,
rotateOrigin: [12, 41]
}).addTo(map);
m.on('dragend', function () {
var p = m.getLatLng();
console.log(p.lat.toFixed(6), p.lng.toFixed(6));
});
注意事项
用 Marker 承载上万个点
每个 Marker 都是一个真实 DOM 节点,加上事件监听与阴影,
三千个左右就开始明显掉帧。点图层超过这个量级,改用
L.circleMarker 配 canvas 渲染器,或者做聚合,见「性能与大量点」。
图标尺寸与高分屏
iconSize 是 CSS 像素。准备了 2 倍图时,尺寸仍按设计稿的 CSS 尺寸写,
例如 48 像素的图片配 iconSize: [24, 24]。
直接把 48 写进尺寸会让图标看起来大一倍。
拖动后坐标是浮点数
dragend 拿到的经纬度有十几位小数。入库或展示前按业务精度取整,
否则同一位置反复拖动会生成大量实质重复的记录。
经纬志(2026)。《Marker 与自定义图标》。https://www.jingweizhi.com/leaflet/tutorial/markers-icons.html(访问日期 2026-11-03)。