> **本文回答**：Marker (DOM) vs Symbol (WebGL) 选型 —— 数量阈值、自定义能力、性能代价。

## 一、对比

| 维度 | Marker (DOM) | Symbol (WebGL) |
|---|---|---|
| 渲染 | HTML/CSS | WebGL 贴图 |
| 数量上限 | < 500 流畅，>2000 严重卡 | 10 万+ 流畅 |
| 自定义 | 100% 随意（div/Vue/React） | 受限（icon + text） |
| 动画 | CSS/JS 自由 | paint 属性插值（有限） |
| 交互 | addEventListener | map.on("click", layerId, ...) |
| 旋转倾斜 | 需 alignment | 原生支持 |
| 碰撞避让 | 手写 | 原生 |
| 图标换色 | CSS | SDF + iconColor |
| Vue/React 组件 | ✓ | ✗ |
| Popup | setPopup | hoverPopup / clickPopup |
| 加载性能 | DOM 构建慢 | 一次 GPU 上传 |

## 二、阈值经验

- 100 以下：Marker OK
- 100 – 500：可以 Marker，但建议加 `removeWhenNoInMapView`
- 500 – 2000：cluster + Marker，或 circle 层
- 2000 – 50000：Symbol
- 50000+：Symbol + cluster + 按 zoom 分层

## 三、决策流

```
点数 < 100 且要 Vue/React？  → Marker
点数 >= 100 且要自定义卡片？ → Marker + removeWhenNoInMapView
点数 > 500？                 → Symbol
要聚合？                     → Symbol + cluster
要热力？                     → heatmap 层
要沿路径显示？               → Symbol + symbol-placement: line
```

## 四、混合：Marker 当选中态 + Symbol 当普通态

```js
// 全量 Symbol
const symbol = new vjmap.Symbol({ data, iconImage: "pin" });
symbol.addTo(map);

let selectedMarker = null;

map.on("click", symbol.getLayerId(), e => {
  const f = e.features[0];
  if (selectedMarker) selectedMarker.remove();
  selectedMarker = new vjmap.Marker({ color: "#f00" })
    .setLngLat(f.geometry.coordinates).addTo(map);

  // 可选：弹出自定义 Popup
  new vjmap.Popup()
    .setLngLat(f.geometry.coordinates)
    .setHTML(`<b>${f.properties.name}</b>`)
    .addTo(map);
});
```

## 五、可交互性对比

### 5.1 hover

Marker：

```js
marker.getElement().addEventListener("mouseenter", () => {});
```

Symbol：

```js
map.on("mouseenter", symbol.getLayerId(), () => {
  map.getCanvas().style.cursor = "pointer";
});
```

或 `symbol.hoverPointer()` / `symbol.hoverFeatureState()`。

### 5.2 click

Marker：

```js
marker.getElement().addEventListener("click", ...);
```

Symbol：

```js
symbol.clickLayer(e => { ... });
// 或
map.on("click", symbol.getLayerId(), e => { ... });
```

## 六、迁移思路

**从 Marker 迁到 Symbol**：

1. 收集所有 Marker 的 lngLat + properties 做成 GeoJSON：
   ```js
   const features = markers.map(m => ({
     type: "Feature",
     geometry: { type: "Point", coordinates: [m.getLngLat().lng, m.getLngLat().lat] },
     properties: { ...m.data }
   }));
   ```
2. 准备图标（loadImage）
3. new Symbol + clickLayer
4. remove 原有 Marker

## 七、各场景推荐

| 场景 | 推荐 |
|---|---|
| 单个/几个站点 | Marker |
| 国产大屏 KPI 热力 | heatmap |
| 几千个设备点位 | Symbol（可 cluster） |
| 轨迹动画 | 一个 Marker + setLngLat |
| 多选列表 + 地图联动 | Symbol + 程序化 selection |
| 表单卡片气泡 | Marker + Popup 或 Symbol + Popup |

## 八、常见坑

1. **误用 Marker 画 5000 点** → 严重卡顿，改 Symbol。
2. **误用 Symbol 放 Vue 组件** → 不支持 DOM；用 Marker 或把 Vue 渲染到 Popup。
3. **Symbol hover 没改色** → feature 没 id，feature-state 不生效。
4. **用 Marker 做热力** → 代码复杂度高；直接 heatmap 层。
