源码分析Openlayers默认键盘交互实现

发布于:2024-12-08 ⋅ 阅读:(286) ⋅ 点赞:(0)

概述

本文主要分析 Openlayers 中用户交互类ol/interactionKeyboardPanKeyboardZoom的源码实现。两者都是 Openlayer 地图中默认提供的功能,都和键盘控制地图有关。

KeyboardPan

KeyboardPan类继承了Interaction类,关于Interaction类后面会讲到,简单将它理解成 Openlayers 中的交互基类就行。

参数

KeyboardPan的参数是一个对象,包含三个属性如下

  • condition:函数,接收mapBrowserEvent作为参数,返回一个布尔值,返回true则表示应该处理该事件,否则不处理,默认是noModifierKeystargetNotEditable
  • duration:动画持续时长,单位毫秒,默认是100(ms)
  • pixelDelta:步长,每按键一次,地图移动的像素,默认是128(px)
核心实现

KeyboardPan的核心代码实现如下:

  handleEvent(mapBrowserEvent) {
    let stopEvent = false;
    if (mapBrowserEvent.type == EventType.KEYDOWN) {
      const keyEvent = /** @type {KeyboardEvent} */ (
        mapBrowserEvent.originalEvent
      );
      const key = keyEvent.key;
      if (
        this.condition_(mapBrowserEvent) &&
        (key == Key.DOWN ||
          key == Key.LEFT ||
          key == Key.RIGHT ||
          key == Key.UP)
      ) {
        const map = mapBrowserEvent.map;
        const view = map.getView();
        const mapUnitsDelta = view.getResolution() * this.pixelDelta_;
        let deltaX = 0,
          deltaY = 0;
        if (key == Key.DOWN) {
          deltaY = -mapUnitsDelta;
        } else if (key == Key.LEFT) {
          deltaX = -mapUnitsDelta;
        } else if (key == Key.RIGHT) {
          deltaX = mapUnitsDelta;
        } else {
          deltaY = mapUnitsDelta;
        }
        const delta = [deltaX, deltaY];
        rotateCoordinate(delta, view.getRotation());
        pan(view, delta, this.duration_);
        keyEvent.preventDefault();
        stopEvent = true;
      }
    }
    return !stopEvent;
  }

handleEvent在按下键盘按键时会触发,先判断通过condition_判断当前元素是否满足条件并且按键是否是方向键,如果满足条件,则通过view.getResolution()获取当前地图的分辨率计算出偏移大小delta,最后调用rotateCoordinatepan方法进行地图的变化。

辅助函数介绍
  • noModifierKeys

noModifierKeys返回一个布尔值,用于检测是否没有按下任何修饰键(如 AltCtrlShiftMeta 键。

其实现如下:

export const noModifierKeys = function (mapBrowserEvent) {
  const originalEvent = /** @type {KeyboardEvent|MouseEvent|TouchEvent} */ (
    mapBrowserEvent.originalEvent
  );
  return (
    !originalEvent.altKey &&
    !(originalEvent.metaKey || originalEvent.ctrlKey) &&
    !originalEvent.shiftKey
  );
};
  • targetNotEditable

targetNotEditable方法用于检测目标元素是否是可编辑的元素,如果是,则返回false,反之返回true

其实现如下:

export const targetNotEditable = function (mapBrowserEvent) {
  const originalEvent = /** @type {KeyboardEvent|MouseEvent|TouchEvent} */ (
    mapBrowserEvent.originalEvent
  );
  const tagName = /** @type {Element} */ (originalEvent.target).tagName;
  return (
    tagName !== "INPUT" &&
    tagName !== "SELECT" &&
    tagName !== "TEXTAREA" &&
    !originalEvent.target.isContentEditable
  );
};
  • rotateCoordinate
    rotateCoordinate方法就是旋转二维坐标,handle方法中调用rotateCoordinate就是传参偏移量和当前地图的倾斜角度。

其实现如下:

export function rotate(coordinate, angle) {
  const cosAngle = Math.cos(angle);
  const sinAngle = Math.sin(angle);
  const x = coordinate[0] * cosAngle - coordinate[1] * sinAngle;
  const y = coordinate[1] * cosAngle + coordinate[0] * sinAngle;
  coordinate[0] = x;
  coordinate[1] = y;
  return coordinate;
}
  • pan
    pan方法就调用 Openlayers 的内部方法view.animateInternal实现地图的平移,就是重新设置地图视图的中心点。

其实现如下:

export function pan(view, delta, duration) {
  const currentCenter = view.getCenterInternal();
  if (currentCenter) {
    const center = [currentCenter[0] + delta[0], currentCenter[1] + delta[1]];
    view.animateInternal({
      duration: duration !== undefined ? duration : 250,
      easing: linear,
      center: view.getConstrainedCenter(center),
    });
  }
}

KeyboardZoom

KeyboardZoomKeyboardPan的实现原理类似,不过KeyboardZoom是通过键盘按键控制地图的缩放。

参数

KeyboardZoom的参数也是一个对象,如下

  • duration:动画持续时长,默认100(ms)
  • condition:条件函数,接受一个MapBrowserEvent参数,返回一个布尔值,辅助判断函数是targetNotEditableplatformModifierKey
  • delta:每按键一次的动画缩放增量,默认为1
核心实现

KeyboardZoom实现如下:

 handleEvent(mapBrowserEvent) {
    let stopEvent = false;
    if (
      mapBrowserEvent.type == EventType.KEYDOWN ||
      mapBrowserEvent.type == EventType.KEYPRESS
    ) {
      const keyEvent = /** @type {KeyboardEvent} */ (
        mapBrowserEvent.originalEvent
      );
      const key = keyEvent.key;
      if (this.condition_(mapBrowserEvent) && (key === '+' || key === '-')) {
        const map = mapBrowserEvent.map;
        const delta = key === '+' ? this.delta_ : -this.delta_;
        const view = map.getView();
        zoomByDelta(view, delta, undefined, this.duration_);
        keyEvent.preventDefault();
        stopEvent = true;
      }
    }
    return !stopEvent;
  }

KeyboardZoom的实现思路和KeyboardZoom大同小异,判断按键是否满足条件函数this.condition_和是否是+或者-键,根据按键判断放大还是缩小,决定了缩放的变化量delta是正数还是负数,最后调用zoomByDelta函数实现地图的缩放。

辅助函数
  • platformModifierKey

platformModifierKe函数在按下Ctrl或者MAC上的meta按键时返回true
其实现如下:

export const platformModifierKey = function (mapBrowserEvent) {
  const originalEvent = /** @type {KeyboardEvent|MouseEvent|TouchEvent} */ (
    mapBrowserEvent.originalEvent
  );
  return MAC ? originalEvent.metaKey : originalEvent.ctrlKey;
};
  • zoomByDelta
    zoomByDelta顾名思义就是根据增量实现地图视图的缩放,通过计算得出地图视图新的分辨率,最后调用view.animate方法进行地图缩放,如果地图视图有动画正在进行,则取消该动画。

其实现如下:

export function zoomByDelta(view, delta, anchor, duration) {
  const currentZoom = view.getZoom();

  if (currentZoom === undefined) {
    return;
  }

  const newZoom = view.getConstrainedZoom(currentZoom + delta);
  const newResolution = view.getResolutionForZoom(newZoom);

  if (view.getAnimating()) {
    view.cancelAnimations();
  }
  view.animate({
    resolution: newResolution,
    anchor: anchor,
    duration: duration !== undefined ? duration : 250,
    easing: easeOut,
  });
}

总结

KeyboardPanKeyboardZoom函数的实现最终是依赖于view.animateInternalview.animate实现,并且还用到了view的一些方法,如下:

  1. view.animateInternal
    • 描述:这是一个内部方法,用于启动视图的动画。它接受配置参数,控制地图动画的持续时间、缓动方式和目标视图属性(如中心点、缩放级别等)。通常由 OpenLayers 内部使用,不建议直接调用。
    • 用法:view.animateInternal(options),其中 options 包括 centerzoomduration 等。
  2. view.animate
    • 描述:animate 方法用于启动地图视图的平移、缩放等动画。它是 OpenLayers 中暴露给开发者的方法,通常用于用户交互时的动画效果,比如地图平移或缩放时的平滑过渡。
    • 用法:view.animate(options)options 包括目标中心、缩放级别、动画持续时间等。
  3. view.getCenterInternal
    • 描述:返回当前视图的中心位置。该方法返回的是一个包含 xy 坐标的数组,表示地图视图当前的中心点。通常由 OpenLayers 内部使用,不建议直接调用。
    • 用法:view.getCenterInternal(),返回当前地图中心的坐标数组,如 [x, y]
  4. view.getConstrainedCenter
    • 描述:返回经过约束后的地图中心坐标。地图中心可能受到地图边界或配置限制,getConstrainedCenter 返回的是在这些限制条件下的有效中心坐标。
    • 用法:view.getConstrainedCenter(center),输入一个中心坐标,返回被限制后的有效坐标。
  5. view.getResolutionForZoom
    • 描述:根据给定的缩放级别返回相应的分辨率。分辨率是地图视图中每个像素表示的地面距离。该方法用于从缩放级别推算相应的地图分辨率。
    • 用法:view.getResolutionForZoom(zoom),输入一个缩放级别(zoom),返回相应的分辨率。
  6. view.getAnimating
    • 描述:检查当前视图是否正在进行动画。如果视图有正在执行的动画(如平移、缩放等),该方法返回 true,否则返回 false
    • 用法:view.getAnimating(),返回一个布尔值,指示当前是否有动画正在进行。
  7. view.cancelAnimations
    • 描述:该方法用于停止或取消正在进行的视图动画。如果视图正在进行平移、缩放等动画,通过调用 cancelAnimations() 可以立即中止这些动画。
      它可以用于在需要时打断动画,或者在用户交互时确保动画被取消。例如,如果你正在执行某种平移或缩放动画,用户突然触发了新的动作(比如平移或缩放),你可能希望取消当前的动画并立即响应新的用户操作。
    • 用法:view.cancelAnimations(); 这个方法没有参数,调用后会立即停止所有正在进行的动画。

网站公告

今日签到

点亮在社区的每一天
去签到