performance-tips
本文回答:Marker 性能优化 —— 数量阈值、viewport 裁剪、降级到 Symbol 图层。
一、Marker 数量对性能的影响
| 数量 | 状态 | 建议 |
|---|---|---|
| < 200 | 流畅 | 任意使用 |
| 200–500 | 略卡 | removeWhenNoInMapView: true |
| 500–2000 | 明显卡 | cluster 或 Symbol layer |
| 2000+ | 严重卡顿 | 必须用 Symbol layer |
数据来源:一个 DOM Marker 是一个 div + CSS;每次 zoom/pan 都会重新定位(transform),几千个就 GC 频繁。
二、视野裁剪
vjmap.createMarker({ removeWhenNoInMapView: true, removeWhenNoInMapViewPadding: 500 })
.setLngLat([lng, lat]).addTo(map);
// removeWhenNoInMapView 是 vjmap 扩展字段:createMarker 内部调 setRemoveWhenNoInMapView;
// 写进 new Marker() 构造参数不生效
- 不在视野的 Marker 从 DOM 移除(真的移除,不是 display:none)
- 进入视野再重新插入 DOM
- 对 2000+ Marker 可以流畅到 500 以下
三、缩小自动缩放
vjmap.createMarker({ scaleMaxZoom: 16 }) // 扩展字段,须走 createMarker
低 zoom 时 div 按比例缩小,减少像素绘制压力。
四、复用 DOM
一个 Marker 被 remove 后,下次 new 时 DOM 会重建。大数量场景推荐对象池:
const pool = [];
function getMarker() {
return pool.pop() || new vjmap.Marker();
}
function releaseMarker(m) {
m.remove();
pool.push(m);
}
五、不要在循环里做 map.project
// 慢
for (const m of markers) {
const p = map.project(m.getLngLat());
...
}
// 快:只在 zoomend / moveend 做
map.on("moveend", () => {
for (const m of markers) {
const p = map.project(m.getLngLat());
...
}
});
六、降级到 Symbol 图层
DOM Marker 无法承受几万点;改用 vjmap Symbol 图层(WebGL):
map.addSource("pts", {
type: "geojson",
data: { type: "FeatureCollection", features: [...] }
});
await map.loadImageEx("pin", "/icons/pin.png");
map.addLayer({
id: "pts-symbol",
type: "symbol",
source: "pts",
layout: {
"icon-image": "pin",
"icon-size": 0.6,
"icon-allow-overlap": true,
"text-field": ["get", "name"],
"text-size": 10,
"text-offset": [0, 1.2],
"text-anchor": "top"
},
paint: { "text-color": "#000", "text-halo-color": "#fff", "text-halo-width": 1 }
});
优点:几十万点依然 60 FPS。
七、Marker vs Symbol 选型
| 特性 | Marker (DOM) | Symbol (WebGL) |
|---|---|---|
| 数量 | < 500 | 任意 |
| 自定义 | 100% 随意 CSS/HTML | 受限(icon + text) |
| 动画 | CSS/JS 自由 | 只能 paint 插值 |
| 交互 | click/hover 原生 | map.on("click", layerId, ...) |
| 旋转倾斜 | 需设置 alignment | 原生支持 |
| 成本 | 高 | 极低 |
八、热力 / 聚合替代 Marker
几万点显示"热度",用 heatmap 图层:
map.addLayer({
id: "heat",
type: "heatmap",
source: "pts",
paint: {
"heatmap-weight": 1,
"heatmap-intensity": 1,
"heatmap-radius": 30
}
});
九、事件节流
map.on("mousemove", throttle(e => {
// 高频事件
}, 100));
function throttle(fn, ms) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last > ms) { fn(...args); last = now; }
};
}
十、常见坑
- 一次性
new vjmap.Marker5000 个 → 页面直接卡死。改为分批或用 Symbol。 - 没 remove 旧 Marker → 每次刷新累积,几次后就爆。
- 每次 move 都 queryRenderedFeatures → 100 ms 节流。
- 用 DOM Marker 做轨迹动画 → 卡顿;轨迹只用一个 Marker + setLngLat。
- 没用
will-change: transform→ 动画帧偶现闪烁。