sync-view
本文回答:多地图视图同步(center/zoom/bearing/pitch)的几种实现与坑。
零、内置同步(推荐)
const unsync = vjmap.syncMaps(map1, map2, map3); // 互相同步 center / zoom / bearing / pitch,内部已防递归
unsync(); // 解绑
卷帘直接用 new vjmap.Compare(mapA, mapB, container, { orientation: "vertical" })(自带滑块并自动同步,remove() 解除)。下面是手写方案。
一、手写同步
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:
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;
});
}
二、封装多图同步
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 方案,不引入额外依赖:
<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>
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 栅格
const map1 = new vjmap.Map({ container: "m1", style: svc.rasterStyle(), ... });
const map2 = new vjmap.Map({ container: "m2", style: svc.vectorStyle(), ... });
同一张图深浅主题对比
// 传 openMapLightStyle / openMapDarkStyle 两次打开
两个 mapid(不同 CAD 图)
两图 mapid 不同时坐标系可能也不同:
- 都用
LnglatProjection:同步就是经纬度对齐。 - 都用
GeoProjection:两个prj独立,同步后 center 只是"经纬度数值相同",但对应的 CAD 坐标不同。可通过prj1.fromLngLat ↔ prj2.toLngLat做桥接。
五、同步内容的细粒度
| 属性 | 是否同步 |
|---|---|
| center | 一般同步 |
| zoom | 一般同步 |
| bearing | 看需求(3D 比对) |
| pitch | 看需求 |
| 图层显隐 | 不要同步(各图独立) |
六、常见坑
- 无限递归:对
move事件同步时不加 guard,两张图互相 fire 导致死循环。 jumpTovseaseTo:同步用 jumpTo(无动画),avoideaseTo的 per-frame 堆叠。- 不同容器像素比:retina 和非 retina 混合时,同 zoom 下实际视觉不同;通常可接受。
- 第三张图加入后旧的 sync 不生效:需要重新调用
linkMaps重建。