Rotate旋转控件源码分析
# 概述
Openlayers 中的Rotate
旋转控件始终指向正北方向,当地图旋转时,控件也会旋转同样角度和方向;另外还可以通过点击控件重置地图的旋转角度。
本文主要介绍Rotate
控件的实现原理。
# 源码分析
Rotate
继承于Control
类,除了响应用户点击外,还需要监听地图的旋转,其实现如下
class Rotate extends Control {
constructor(options) {
options = options ? options : {};
super({
element: document.createElement("div"),
render: options.render,
target: options.target,
});
const className =
options.className !== undefined ? options.className : "ol-rotate";
const label = options.label !== undefined ? options.label : "\u21E7";
const compassClassName =
options.compassClassName !== undefined
? options.compassClassName
: "ol-compass";
this.label_ = null;
if (typeof label === "string") {
this.label_ = document.createElement("span");
this.label_.className = compassClassName;
this.label_.textContent = label;
} else {
this.label_ = label;
this.label_.classList.add(compassClassName);
}
const tipLabel = options.tipLabel ? options.tipLabel : "Reset rotation";
const button = document.createElement("button");
button.className = className + "-reset";
button.setAttribute("type", "button");
button.title = tipLabel;
button.appendChild(this.label_);
button.addEventListener(
EventType.CLICK,
this.handleClick_.bind(this),
false
);
const cssClasses =
className + " " + CLASS_UNSELECTABLE + " " + CLASS_CONTROL;
const element = this.element;
element.className = cssClasses;
element.appendChild(button);
this.callResetNorth_ = options.resetNorth ? options.resetNorth : undefined;
this.duration_ = options.duration !== undefined ? options.duration : 250;
this.autoHide_ = options.autoHide !== undefined ? options.autoHide : true;
this.rotation_ = undefined;
if (this.autoHide_) {
this.element.classList.add(CLASS_HIDDEN);
}
}
handleClick_(event) {
event.preventDefault();
if (this.callResetNorth_ !== undefined) {
this.callResetNorth_();
} else {
this.resetNorth_();
}
}
resetNorth_() {
const map = this.getMap();
const view = map.getView();
if (!view) {
return;
}
const rotation = view.getRotation();
if (rotation !== undefined) {
if (this.duration_ > 0 && rotation % (2 * Math.PI) !== 0) {
view.animate({
rotation: 0,
duration: this.duration_,
easing: easeOut,
});
} else {
view.setRotation(0);
}
}
}
render(mapEvent) {
const frameState = mapEvent.frameState;
if (!frameState) {
return;
}
const rotation = frameState.viewState.rotation;
if (rotation != this.rotation_) {
const transform = "rotate(" + rotation + "rad)";
if (this.autoHide_) {
const contains = this.element.classList.contains(CLASS_HIDDEN);
if (!contains && rotation === 0) {
this.element.classList.add(CLASS_HIDDEN);
} else if (contains && rotation !== 0) {
this.element.classList.remove(CLASS_HIDDEN);
}
}
this.label_.style.transform = transform;
}
this.rotation_ = rotation;
}
}
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
# Rotate
控件的参数
Rotate
控件的构造函数接受一个参数options
对象,该对象有如下属性
className
:控件的类名,默认为ol-rotate
label
:控件的显示标签图标,默认为\u21E7
compassClassName
:控件图标类名,默认为ol-compass
tipLabel
:控件hover
时的显示,默认为Reset rotation
resetNorth
:点击控件后的回调事件,默认为undefined
duration
: 地图动画的持续时长,默认为250
,表示250 毫秒autoHide
: 自动隐藏,默认为true
,初始化时隐藏控件
Rotate
类的构造函数内部会调用父类super({ element: document.createElement('div'),render: options.render,target: options.target,})
,默认情况下,render
和target
都不存在,然后采用上述的默认值,创建一个按钮及其符号,当调用setMap
方法时会将它添加到overlay container
中;通过元素的addEventListener
注册Rotate
控件的点击handleClick_
事件;默认情况下this.callRestNorth_
为undefined
,初始化this.rotation_
为undefined
;最后判断this.autoHide_
是否为true
,默认为true
,就会给控件加上一个类名ol-hidden
。
# Rotate
类的方法
Rotate
类的方法也不多,有如下三个:
handleClick_
方法
handleClick_
方法会判断this.callResetNorth
是否为true
,因为开发时可以通过参数options.resetNorth
自定义点击事件,默认情况下,点击会执行resetNorth_
方法
resetNorth_
方法
resetNorth_
方法内部就是获取当前视图的旋转角度,默认情况下会调用view.animate
调整地图视图旋转角度为0
;若this.duration_
动画持续时长为0或者旋转的角度为360°倍数,则调用view.setRotation
重置地图视图。
render
方法
render
方法会覆盖Rotate
的父类Control
中的render
方法,该方法就是用于渲染控件的;默认情况下frameState.viewState.rotation
为0
,即此时地图未旋转,而this.rotation_
为undefined
,因此rotation
不等于this.rotation_
,继续判断this.autoHide
,默认为true
,判断控件的类名是否包含ol-hide
,若不包含,且rotation
为0
,则给控件加上类名ol-hide
,隐藏控件;反之,若控件的类名包含ol-hide
且rotation
不为0
,则移除类名ol-hide
,显示控件;然后将控件的label
图标进行旋转,角度即为rotation
;最后将rotation
赋值给this.rotation_
。
前面提到Rotate
控件会跟随地图视图的旋转,继而控件图标也跟随同方向旋转同样角度,就是调用的render
方法,那么render
方法是如何调用的呢?
# render
方法触发机制
render
方法实际上是在父类Control
类中调用的,在Control
类一文中提过 Control
类的setMap
方法调用后,会判断this.render
是否是个空方法,显然Rotate
控件中重新定义了render
方法,于是 Openlayers 会调用listen
方法注册postrender
类型的监听事件,而回调方法就是this.render
;而调用map.render()
方法进行渲染时,会调用animationDelay_
方法,在该方法中又会调用renderFrame_
方法,在renderFrame_
方法中会执行这行代码this.dispatchEvent(new MapEvent(MapEventType.POSTRENDER, this, frameState));
进行postrender
类型的注册事件的派发,这就会执行this.render
方法,从而实现Rotate
控件的实时同步旋转。
# 总结
Rotate
控件是基于Control
类实现的,而其实时同步旋转的逻辑还是addEventListener
(listen
中调用注册的方法)和dispatchEvent
那一套机制。