Jinuss's blog Jinuss's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

前端可视化
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • React

  • Vue

  • JavaScript文章

  • 学习笔记

  • openlayers

    • Openlayers用图片填充面
    • Openlayers用图片填充线
    • openlayers处理大量Overlay渲染问题
    • openlayers的比例尺
    • openlayers水印
    • openlayer实现矢量图层点击高亮效果
    • openlayers实现自定义路径
    • Openlayers加载渲染矢量切片
    • openlayers实现角度测量
    • openlayers实现长度测量
    • openlayers实现面积测量
    • Openlayers实现方位角测量
    • Openlayers中的动画
    • Openlayers的多边形高级交互
    • Openlayers地图底图换色
    • Openlayers种的默认交互事件
    • Openlayers默认键盘交互实现
      • 源码分析之Openlayers中默认键盘事件触发机制
      • 源码分析之Openlayers中的Collection类
    • threejs

    • MapboxGL

    • 工具

    • 源码合集

    • 前端
    • openlayers
    东流
    2024-11-26
    目录

    Openlayers默认键盘交互实现

    # 概述

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

    # KeyboardPan

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

    # 参数

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

    • condition:函数,接受mapBrowserEvent作为参数,返回一个布尔值,返回true则表示应该处理该事件,否则不处理,默认是noModifierKeys和targetNotEditable
    • 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;
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37

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

    # 辅助函数介绍

    • noModifierKeys

    noModifierKeys返回一个布尔值,用于检测是否没有按下任何修饰键(如 Alt、Ctrl、Shift 或 Meta 键。

    其实现如下:

    export const noModifierKeys = function (mapBrowserEvent) {
      const originalEvent = /** @type {KeyboardEvent|MouseEvent|TouchEvent} */ (
        mapBrowserEvent.originalEvent
      );
      return (
        !originalEvent.altKey &&
        !(originalEvent.metaKey || originalEvent.ctrlKey) &&
        !originalEvent.shiftKey
      );
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    • 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
      );
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    • 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;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    • 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),
        });
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    # KeyboardZoom

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

    # 参数

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

    • duration:动画持续时长,默认100(ms)
    • condition:条件函数,接受一个MapBrowserEvent参数,返回一个布尔值,辅助判断函数是targetNotEditable和platformModifierKey
    • 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;
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    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;
    };
    
    1
    2
    3
    4
    5
    6
    • 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,
      });
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    # 总结

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

    1. view.animateInternal
      • 描述:这是一个内部方法,用于启动视图的动画。它接受配置参数,控制地图动画的持续时间、缓动方式和目标视图属性(如中心点、缩放级别等)。通常由 OpenLayers 内部使用,不建议直接调用。
      • 用法:view.animateInternal(options),其中 options 包括 center、zoom、duration 等。
    2. view.animate
      • 描述:animate 方法用于启动地图视图的平移、缩放等动画。它是 OpenLayers 中暴露给开发者的方法,通常用于用户交互时的动画效果,比如地图平移或缩放时的平滑过渡。
      • 用法:view.animate(options),options 包括目标中心、缩放级别、动画持续时间等。
    3. view.getCenterInternal
      • 描述:返回当前视图的中心位置。该方法返回的是一个包含 x 和 y 坐标的数组,表示地图视图当前的中心点。通常由 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(); 这个方法没有参数,调用后会立即停止所有正在进行的动画。
    编辑 (opens new window)
    上次更新: 2024/12/03, 14:43:37
    Openlayers种的默认交互事件
    源码分析之Openlayers中默认键盘事件触发机制

    ← Openlayers种的默认交互事件 源码分析之Openlayers中默认键盘事件触发机制→

    最近更新
    01
    GeoJSON
    05-08
    02
    Circle
    04-15
    03
    CircleMarker
    04-15
    更多文章>
    Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式