唯杰地图 logo唯杰地图
VJMAP
EN

drag-rotate-zoom

vjmap/references/interaction/drag-rotate-zoom.md 5.3 KB142 行
原文

本文回答:地图交互控制开关 —— 拖动 / 旋转 / 缩放 / 双击 / 触摸 / 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。

十二、常见坑

  1. 用户滚轮误缩放 → map.scrollZoom.disable() 并提供按钮缩放。

  2. maxBounds 太紧 → pan 抖动 → 适当放宽 bound 边界(例如 +10% padding)。

  3. flyTo 过远 → 花很长时间:调 speed 和 curve,或改用 jumpTo。

  4. 双击缩放与自定义双击冲突 → doubleClickZoom: false,自己监听 dblclick。

  5. 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。

  6. MapEvent 族四件同样只声明不导出:vjmap.MapEvent/MapMouseEvent/MapTouchEvent/MapWheelEvent 运行时全 undefined(min.js 里连标识符都被 shake 掉)。事件回调拿到的是内部构造实例(click 载荷 ctor=Bn,键=point,lngLat,originalEvent,type,_defaultPrevented,target,preventDefault()/defaultPrevented 可用)——字段名与文档一致,只是类名不可达。

  7. 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 是同步往返,不受此影响。