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