drag-rotate-zoom
本文回答:地图交互控制开关 —— 拖动 / 旋转 / 缩放 / 双击 / 触摸 / pitch。
一、控制对象(vjmap 手势)
| 交互 | map 属性 |
|---|---|
| 拖动平移 | map.dragPan |
| 按住鼠标旋转 | map.dragRotate |
| 滚轮缩放 | map.scrollZoom |
| 键盘 | map.keyboard |
| 双击缩放 | map.doubleClickZoom |
| 触摸缩放 | map.touchZoomRotate |
| 触摸 pitch | map.touchPitch |
| 边界 box zoom | map.boxZoom |
每个对象都有 enable(), disable(), isEnabled()。
二、创建时禁用
new vjmap.Map({
container: "map",
style: ...,
// 在构造器里关掉
dragPan: false,
dragRotate: false,
scrollZoom: false,
keyboard: false,
doubleClickZoom: false,
touchZoomRotate: false,
boxZoom: false
});
三、动态开关
map.scrollZoom.disable();
map.scrollZoom.enable();
console.log(map.scrollZoom.isEnabled());
// 只允许缩放,不允许旋转
map.dragRotate.disable();
map.touchZoomRotate.disableRotation();
四、限制最大 / 最小 zoom
map.setMinZoom(0);
map.setMaxZoom(22);
// 构造时
new vjmap.Map({ minZoom: 12, maxZoom: 18, ... });
五、限制 pitch 范围
map.setMinPitch(0);
map.setMaxPitch(85); // 默认最大 85(引擎上限也是 85)
// 禁止 pitch:
map.setMaxPitch(0);
六、限制 bearing
无直接 setMaxBearing。可以 map.on("rotate", () => { if (map.getBearing() > 45) map.setBearing(45); })。
七、限制平移范围
map.setMaxBounds(map.toLngLat(prj.getMapExtent()));
// 或
map.setMaxBounds([[lngW, latS], [lngE, latN]]);
// 取消限制
map.setMaxBounds(null);
八、触摸交互
map.touchZoomRotate.enable();
map.touchZoomRotate.disableRotation(); // 禁双指旋转但保留缩放
map.touchPitch.enable();
九、程序化动画
map.jumpTo({ center: [0,0], zoom: 10 }); // 瞬间
map.panTo([lng, lat], { duration: 1000 });
map.zoomTo(12, { duration: 500 });
map.rotateTo(60, { duration: 500 });
map.fitBounds([[-1, -1], [1, 1]], { padding: 50, duration: 800 });
map.flyTo({
center: [lng, lat],
zoom: 14, pitch: 60, bearing: -20,
speed: 1.2, curve: 1.4,
essential: true
});
map.fitMapBounds(); // vjmap 扩展,全图
essential: true 把动画标记为"必要"——不受 prefers-reduced-motion 影响(用户开了减少动态效果也照常播放);缺省/false 时系统设置会减少动画。
十、判断是否交互中
if (map.isMoving() || map.isZooming() || map.isRotating()) {
// 正在动
}
// vjmap 扩展:
map.setIsInteracting(true); // 手动标记
map.setIsInteracting(false);
十一、性能 trick
-
动画期间关闭贵重渲染:
map.on("movestart", () => map.setLayoutProperty("my-heavy-layer", "visibility", "none")); map.on("moveend", () => map.setLayoutProperty("my-heavy-layer", "visibility", "visible")); -
用
map.easeTo({ center, duration: 0 })代替jumpTo避免某些 GL bug。
十二、常见坑
-
用户滚轮误缩放 →
map.scrollZoom.disable()并提供按钮缩放。 -
maxBounds 太紧 → pan 抖动 → 适当放宽 bound 边界(例如 +10% padding)。
-
flyTo过远 → 花很长时间:调speed和curve,或改用jumpTo。 -
双击缩放与自定义双击冲突 →
doubleClickZoom: false,自己监听dblclick。 -
Handler 类本身不导出:
map.boxZoom/scrollZoom/dragPan/dragRotate/keyboard/doubleClickZoom/touchZoomRotate/touchPitch八个实例的isEnabled()/enable()/disable()/isActive()全部可用(enable/disable 往返true→false→true),但vjmap.BoxZoomHandler/ScrollZoomHandler/DragPanHandler/DragRotateHandler/KeyboardHandler/DoubleClickZoomHandler/TouchZoomRotateHandler/TouchPitchHandler八个类在运行时全是undefined(d.ts 声明为export class,产物里未导出)——只能经map.*实例操作,不能new也不能instanceof。 -
MapEvent 族四件同样只声明不导出:
vjmap.MapEvent/MapMouseEvent/MapTouchEvent/MapWheelEvent运行时全undefined(min.js 里连标识符都被 shake 掉)。事件回调拿到的是内部构造实例(click 载荷 ctor=Bn,键=point,lngLat,originalEvent,type,_defaultPrevented,target,preventDefault()/defaultPrevented可用)——字段名与文档一致,只是类名不可达。 -
zoomIn/zoomOut/resetNorth/snapToNorth 是 easeTo 族动画方法:调用后立即
getZoom()/getBearing()读到的是动画前值(zoomIn 后 zoom 数字不变是正常——动画帧推进中);setBearing/setPadding则走jumpTo,立即生效、无动画(调用后马上getBearing()即新值)。这些方法都同步派发move系列事件——若页面上残留失效的 store/draw 监听器,异常会同步穿透回 camera 调用栈(setBearing/setPadding抛Cannot read properties of null (reading 'getAll')即源于此类监听污染,非方法本身缺陷)。断言相机状态要等moveend/idle事件或延时后再读。setMaxBounds/getMaxBounds/cameraForBounds/fitScreenCoordinates/getFreeCameraOptions/setRenderWorldCopies是同步往返,不受此影响。