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
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
类,从而管理键盘事件监听。
# 核心实现解析
初始化配置 将配置的
keyboardPanDelta
转换为具体的按键映射事件钩子管理
- 焦点控制:通过设置
tabIndex
使地图容器可接受键盘事件 - 事件委托:在地图获得焦点时激活键盘监听(
_addHooks
),失焦时移除(_removeHooks
)
- 焦点控制:通过设置
按键映射表 支持多键绑定(如
+
和=
)均可触发放大平移与缩放的按键处理
- 动态映射:根据配置的
panDelta
和zoomDelta
生成按键对应的操作值
- 动态映射:根据配置的
按键事件处理逻辑
- 加速机制:按下
shift
键时平移速度*3
,缩放步长*3
- 边界限制:平移时考虑
maxBounds
限制 - 无限滚动:
worldCopyJump
模式下,处理地图滚动边界 - 事件阻止:阻止默认行为,避免浏览器默认行为干扰
- 加速机制:按下
# 关键设计亮点
- 响应式焦点管理
- 通过监听容器
focus/blur
自动激活/停用键盘事件,避免与其他页面元素冲突。
- 可扩展的键位映射
- 修改
keyCodes
对象即可自定义按键绑定,例如将方向键改为WASD
。
- 性能优化
- 使用
_panAnim._inProgress
检查当前是否有动画进行,避免重复触发。
- 组合键兼容性
- 忽略
Alt/Ctrl/Cmd
的组合键事件,避免与浏览器快捷键冲突。
# 总结
Keyboard
类提供了一种简单易用的键盘交互方式,通过配置项和事件处理器,实现了地图的平移和缩放功能。它的设计优雅、灵活,适用于各种场景,是 Leaflet 中一个非常实用的功能模块。核心实现主要是通过map.panTo
、map.panBy
和map.setZoom
方法。
编辑 (opens new window)
上次更新: 2025/04/16, 05:17:33