> **本文回答**：多地图视图同步（center/zoom/bearing/pitch）的几种实现与坑。

## 零、内置同步（推荐）

```js
const unsync = vjmap.syncMaps(map1, map2, map3);   // 互相同步 center / zoom / bearing / pitch，内部已防递归
unsync();                                           // 解绑
```

卷帘直接用 `new vjmap.Compare(mapA, mapB, container, { orientation: "vertical" })`（自带滑块并自动同步，`remove()` 解除）。下面是手写方案。

## 一、手写同步

```js
function syncMove(source, target) {
  source.on("move", () => {
    target.jumpTo({
      center:  source.getCenter(),
      zoom:    source.getZoom(),
      bearing: source.getBearing(),
      pitch:   source.getPitch()
    });
  });
}

// 双向
syncMove(map1, map2);
syncMove(map2, map1);
```

注意双向会 **无限递归**：`jumpTo` 内部也会 fire move。加 guard：

```js
let syncing = false;
function syncMove(src, dst) {
  src.on("move", () => {
    if (syncing) return;
    syncing = true;
    dst.jumpTo({
      center:  src.getCenter(),
      zoom:    src.getZoom(),
      bearing: src.getBearing(),
      pitch:   src.getPitch()
    });
    syncing = false;
  });
}
```

## 二、封装多图同步

```js
function linkMaps(...maps) {
  let syncing = false;
  maps.forEach(src => {
    src.on("move", () => {
      if (syncing) return;
      syncing = true;
      const { lng, lat } = src.getCenter();
      const zoom = src.getZoom();
      const bearing = src.getBearing();
      const pitch = src.getPitch();
      maps.filter(m => m !== src)
          .forEach(m => m.jumpTo({ center: [lng, lat], zoom, bearing, pitch }));
      syncing = false;
    });
  });
}
linkMaps(map1, map2, map3);
```

## 三、卷帘（Compare）

推荐自建 DOM 方案，不引入额外依赖：

```html
<div id="container" style="position:relative;width:100%;height:100%">
  <div id="before" style="position:absolute;inset:0"></div>
  <div id="after"  style="position:absolute;inset:0;clip-path:inset(0 50% 0 0)"></div>
  <div id="handle" style="position:absolute;top:0;bottom:0;left:50%;width:4px;background:#000;cursor:ew-resize;z-index:10"></div>
</div>
```

```js
linkMaps(mapBefore, mapAfter);

const handle = document.getElementById("handle");
const after  = document.getElementById("after");
let dragging = false;
handle.onmousedown = () => { dragging = true; };
window.onmouseup   = () => { dragging = false; };
window.onmousemove = e => {
  if (!dragging) return;
  const rect = container.getBoundingClientRect();
  const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
  handle.style.left = ratio * 100 + "%";
  after.style.clipPath = `inset(0 ${(1 - ratio) * 100}% 0 0)`;
};
```

## 四、与 vjmap CAD 的结合

### 同一张图的 GeomRender vs 栅格

```js
const map1 = new vjmap.Map({ container: "m1", style: svc.rasterStyle(), ... });
const map2 = new vjmap.Map({ container: "m2", style: svc.vectorStyle(), ... });
```

### 同一张图深浅主题对比

```js
// 传 openMapLightStyle / openMapDarkStyle 两次打开
```

### 两个 mapid（不同 CAD 图）

两图 mapid 不同时坐标系可能也不同：
- 都用 `LnglatProjection`：同步就是经纬度对齐。
- 都用 `GeoProjection`：两个 `prj` 独立，同步后 center 只是"经纬度数值相同"，但对应的 CAD 坐标不同。可通过 `prj1.fromLngLat ↔ prj2.toLngLat` 做桥接。

## 五、同步内容的细粒度

| 属性 | 是否同步 |
|---|---|
| center | 一般同步 |
| zoom | 一般同步 |
| bearing | 看需求（3D 比对） |
| pitch | 看需求 |
| 图层显隐 | 不要同步（各图独立） |

## 六、常见坑

1. **无限递归**：对 `move` 事件同步时不加 guard，两张图互相 fire 导致死循环。
2. **`jumpTo` vs `easeTo`**：同步用 jumpTo（无动画），avoid `easeTo` 的 per-frame 堆叠。
3. **不同容器像素比**：retina 和非 retina 混合时，同 zoom 下实际视觉不同；通常可接受。
4. **第三张图加入后旧的 sync 不生效**：需要重新调用 `linkMaps` 重建。
