Jinuss's blog Jinuss's blog
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

Web、WebGIS技术博客
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 框架

  • core模块

  • dom模块

  • control

  • geometry

  • geo

  • layer

  • Map

    • Map类
    • Map类扩展方法之BoxZoom
    • Map类扩展方法之DoubleClickZoom
    • Map类扩展方法之Drag
    • Map类扩展方法之Keyboard
      • 概述
      • 源码分析
        • 源码实现
        • 源码分析概览
        • 核心实现解析
        • 关键设计亮点
      • 总结
    • Map类扩展方法之ScrollWheelZoom
    • Map类扩展方法之TapHold
    • Map类扩展方法之TouchZoom
  • 《Leaflet源码》笔记
  • Map
东流
2025-04-07
目录

Map类扩展方法之Keyboard

# 概述

键盘操作是地图的一个常用功能,Leaflet 提供了一个 Keyboard 类,用于实现键盘交互控制地图平移和缩放。

# 源码分析

# 源码实现

Keyboard的实现如下

Map.mergeOptions({
  keyboard: true, // 是否启用键盘交互
  keyboardPanDelta: 80, // 每次按键平移的像素增量,单位:px
});

export var Keyboard = Handler.extend({
  keyCodes: {
    left: [37], //左箭头
    right: [39], // 右箭头
    down: [40], // 下箭头
    up: [38], // 上箭头
    zoomIn: [187, 107, 61, 171], // +/=键
    zoomOut: [189, 109, 54, 173], // -/_键
  },
  initialize: function (map) {
    this._map = map;
    this._setPanDelta(map.options.keyboardPanDelta); //初始化平移量
    this._setZoomDelta(map.options.zoomDelta); //初始化缩放量
  },

  addHooks: function () {
    var container = this._map._container;
    // 确保地图容器可聚焦
    if (container.tabIndex <= 0) {
      container.tabIndex = "0";
    }

    // 绑定容器聚焦/失焦、鼠标按下事件
    on(
      container,
      {
        focus: this._onFocus,
        blur: this._onBlur,
        mousedown: this._onMouseDown,
      },
      this
    );

    // 绑定地图的焦点事件
    this._map.on(
      {
        focus: this._addHooks,
        blur: this._removeHooks,
      },
      this
    );
  },
  removeHooks: function () {
    this._removeHooks();

    off(
      this._map._container,
      {
        focus: this._onFocus,
        blur: this._onBlur,
        mousedown: this._onMouseDown,
      },
      this
    );

    this._map.off(
      {
        focus: this._addHooks,
        blur: this._removeHooks,
      },
      this
    );
  },
  _onMouseDown: function () {
    if (this._focused) {
      return;
    }

    var body = document.body,
      docEl = document.documentElement,
      top = body.scrollTop || docEl.scrollTop,
      left = body.scrollLeft || docEl.scrollLeft;

    this._map._container.focus();

    window.scrollTo(left, top);
  },

  _onFocus: function () {
    this._focused = true;
    this._map.fire("focus");
  },
  _onBlur: function () {
    this._focused = false;
    this._map.fire("blur");
  },
  _setPanDelta: function (panDelta) {
    // 设置每个按键的平移像素为一个数组[x,y],x为水平方向平移量,y为垂直方向平移量
    var keys = (this._panKeys = {}),
      codes = this.keyCodes,
      i,
      len;
   
    for (i = 0, len = codes.left.length; i < len; i++) {
      keys[codes.left[i]] = [-1 * panDelta, 0];
    }
    for (i = 0, len = codes.right.length; i < len; i++) {
      keys[codes.right[i]] = [panDelta, 0];
    }
    for (i = 0, len = codes.down.length; i < len; i++) {
      keys[codes.down[i]] = [0, panDelta];
    }
    for (i = 0, len = codes.up.length; i < len; i++) {
      keys[codes.up[i]] = [0, -1 * panDelta];
    }
  },
  _setZoomDelta: function (zoomDelta) {
    // 设置每个按键的缩放量
    var keys = (this._zoomKeys = {}),
      codes = this.keyCodes,
      i,
      len;

    for (i = 0, len = codes.zoomIn.length; i < len; i++) {
      keys[codes.zoomIn[i]] = zoomDelta;
    }
    for (i = 0, len = codes.zoomOut.length; i < len; i++) {
      keys[codes.zoomOut[i]] = -zoomDelta;
    }
  },

  _addHooks: function () {
    on(document, "keydown", this._onKeyDown, this);
  },

  _removeHooks: function () {
    off(document, "keydown", this._onKeyDown, this);
  },

  _onKeyDown: function (e) {
    // 忽略组合键 (Alt/Ctrl/Cmd)
    if (e.altKey || e.ctrlKey || e.metaKey) {
      return;
    }

    var key = e.keyCode,
      map = this._map,
      offset;
    
    // 处理平移
    if (key in this._panKeys) {
      if (!map._panAnim || !map._panAnim._inProgress) {
        offset = this._panKeys[key];
        if (e.shiftKey) { // Shift加速
          offset = toPoint(offset).multiplyBy(3); 
        }
        //限制地图平移范围
        if (map.options.maxBounds) {
          offset = map._limitOffset(toPoint(offset), map.options.maxBounds);
        }
        // 处理无线地图滚动
        if (map.options.worldCopyJump) {
          var newLatLng = map.wrapLatLng(
            map.unproject(map.project(map.getCenter()).add(offset))
          );
          map.panTo(newLatLng);
        } else {
          map.panBy(offset);
        }
      }
    } else if (key in this._zoomKeys) {
      // 处理缩放
      map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
    } else if (
      key === 27 &&
      map._popup &&
      map._popup.options.closeOnEscapeKey
    ) {
      // 处理ESC键关闭弹窗
      map.closePopup();
    } else {
      return;
    }
    // 阻止事件冒泡
    stop(e);
  },
});

Map.addInitHook("addHandler", "keyboard", Keyboard);
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

# 源码分析概览

1. 全局配置合并

将键盘控制的默认配置项合并到地图选项中,用户可通过map.options覆盖

2. 键盘处理类

Keyboard类继承自Handler类,遵循 Leaflet 事件处理器标准结构,通过addHooks/removeHooks管理事件监听。

3. 地图初始化挂载

通过Map.addInitHook方法将Keyboard类注册到钩子函数中,实例化地图对象时,就会默认实例化Keyboard类,从而管理键盘事件监听。

# 核心实现解析

  1. 初始化配置 将配置的keyboardPanDelta转换为具体的按键映射

  2. 事件钩子管理

    • 焦点控制:通过设置tabIndex使地图容器可接受键盘事件
    • 事件委托:在地图获得焦点时激活键盘监听(_addHooks),失焦时移除(_removeHooks)
  3. 按键映射表 支持多键绑定(如+和=)均可触发放大

  4. 平移与缩放的按键处理

    • 动态映射:根据配置的panDelta和zoomDelta生成按键对应的操作值
  5. 按键事件处理逻辑

    • 加速机制:按下shift键时平移速度*3,缩放步长*3
    • 边界限制:平移时考虑maxBounds限制
    • 无限滚动:worldCopyJump模式下,处理地图滚动边界
    • 事件阻止:阻止默认行为,避免浏览器默认行为干扰

# 关键设计亮点

  1. ​​响应式焦点管理​​
  • 通过监听容器 focus/blur 自动激活/停用键盘事件,避免与其他页面元素冲突。
  1. 可扩展的键位映射​​
  • 修改 keyCodes 对象即可自定义按键绑定,例如将方向键改为 WASD。 ​​
  1. 性能优化​​
  • 使用 _panAnim._inProgress 检查当前是否有动画进行,避免重复触发。 ​​
  1. 组合键兼容性​​
  • 忽略 Alt/Ctrl/Cmd 的组合键事件,避免与浏览器快捷键冲突。

# 总结

Keyboard类提供了一种简单易用的键盘交互方式,通过配置项和事件处理器,实现了地图的平移和缩放功能。它的设计优雅、灵活,适用于各种场景,是 Leaflet 中一个非常实用的功能模块。核心实现主要是通过map.panTo、map.panBy和map.setZoom方法。

编辑 (opens new window)
上次更新: 2025/04/16, 05:17:33
Map类扩展方法之Drag
Map类扩展方法之ScrollWheelZoom

← Map类扩展方法之Drag Map类扩展方法之ScrollWheelZoom→

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