OpenLayers 6 + webpack 通過源碼分析來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的自定義控件
晚上鼓搗webpack環(huán)境的時(shí)候,忽然心血來潮,想試試實(shí)現(xiàn)一個(gè)自定義的地圖控件。其實(shí)網(wǎng)絡(luò)上已經(jīng)有很多相關(guān)的教程,實(shí)現(xiàn)思路大多是采用jQuery做一個(gè)UI組件外掛上去。不過這種方式實(shí)現(xiàn)的控件可重用性不是很高。
為了有別于其他人的方案,達(dá)到可重用的要求,我采用的是通過繼承ol.control類的方式來實(shí)現(xiàn)。
基本思路:
通過閱讀OpenLayers的源碼,仿制一個(gè)點(diǎn)擊之后可以彈出一個(gè)警告框的控件。
實(shí)現(xiàn)步驟:
先看一看OpenLayers的源碼。為了簡(jiǎn)化問題,我找了最簡(jiǎn)單的FullScreen的控件源碼來參考:
import Control from './Control.js';
import {CLASS_CONTROL, CLASS_UNSELECTABLE, CLASS_UNSUPPORTED} from '../css.js';
import {replaceNode} from '../dom.js';
import {listen} from '../events.js';
import EventType from '../events/EventType.js';
const events = ['fullscreenchange', 'webkitfullscreenchange', 'MSFullscreenChange'];
/**
* @typedef {Object} Options
* @property {string} [className='ol-full-screen'] CSS class name.
* @property {string|Text} [label='\u2922'] Text label to use for the button.
* Instead of text, also an element (e.g. a `span` element) can be used.
* @property {string|Text} [labelActive='\u00d7'] Text label to use for the
* button when full-screen is active.
* Instead of text, also an element (e.g. a `span` element) can be used.
* @property {string} [tipLabel='Toggle full-screen'] Text label to use for the button tip.
* @property {boolean} [keys=false] Full keyboard access.
* @property {HTMLElement|string} [target] Specify a target if you want the
* control to be rendered outside of the map's viewport.
* @property {HTMLElement|string} [source] The element to be displayed
* fullscreen. When not provided, the element containing the map viewport will
* be displayed fullscreen.
*/
class FullScreen extends Control {
/**
* @param {Options=} opt_options Options.
*/
constructor(opt_options) {
const options = opt_options ? opt_options : {};
super({
element: document.createElement('div'),
target: options.target
});
/**
* @private
* @type {string}
*/
this.cssClassName_ = options.className !== undefined ? options.className :
'ol-full-screen';
const label = options.label !== undefined ? options.label : '\u2922';
/**
* @private
* @type {Text}
*/
this.labelNode_ = typeof label === 'string' ?
document.createTextNode(label) : label;
const labelActive = options.labelActive !== undefined ? options.labelActive : '\u00d7';
/**
* @private
* @type {Text}
*/
this.labelActiveNode_ = typeof labelActive === 'string' ?
document.createTextNode(labelActive) : labelActive;
/**
* @private
* @type {HTMLElement}
*/
this.button_ = document.createElement('button');
const tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen';
this.setClassName_(this.button_, isFullScreen());
this.button_.setAttribute('type', 'button');
this.button_.title = tipLabel;
this.button_.appendChild(this.labelNode_);
this.button_.addEventListener(EventType.CLICK, this.handleClick_.bind(this), false);
const cssClasses = this.cssClassName_ + ' ' + CLASS_UNSELECTABLE +
' ' + CLASS_CONTROL + ' ' +
(!isFullScreenSupported() ? CLASS_UNSUPPORTED : '');
const element = this.element;
element.className = cssClasses;
element.appendChild(this.button_);
/**
* @private
* @type {boolean}
*/
this.keys_ = options.keys !== undefined ? options.keys : false;
/**
* @private
* @type {HTMLElement|string|undefined}
*/
this.source_ = options.source;
}
/**
* @param {MouseEvent} event The event to handle
* @private
*/
handleClick_(event) {
event.preventDefault();
this.handleFullScreen_();
}
……
直接看一下構(gòu)造函數(shù):這里是對(duì)初始化的時(shí)候所用的配置項(xiàng)的處理,以及對(duì)父類中的成員進(jìn)行初始化的過程,可以不用動(dòng)。
constructor(opt_options) {
const options = opt_options ? opt_options : {};
super({
element: document.createElement('div'),
target: options.target
});
接下來是定義控件的css樣式,也不用動(dòng):
/**
* @private
* @type {string}
*/
this.cssClassName_ = options.className !== undefined ? options.className :
'ol-full-screen';
接下來是定義按鈕上的文字標(biāo)簽,原來的FullScreen控件定義了兩個(gè),對(duì)應(yīng)是否全屏的兩個(gè)狀態(tài),我們后面只需要定義一個(gè)就行了:
const label = options.label !== undefined ? options.label : '\u2922';
/**
* @private
* @type {Text}
*/
this.labelNode_ = typeof label === 'string' ?
document.createTextNode(label) : label;
const labelActive = options.labelActive !== undefined ? options.labelActive : '\u00d7';
/**
* @private
* @type {Text}
*/
this.labelActiveNode_ = typeof labelActive === 'string' ?
document.createTextNode(labelActive) : labelActive;
下面幾行代碼功能比較密集,在代碼注釋里面加以說明:
/**
* @private
* @type {HTMLElement}
*/
//創(chuàng)建控件的HTML控件,是一個(gè)按鈕
this.button_ = document.createElement('button');
//為這個(gè)按鈕初始化hint文字
const tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen';
//根據(jù)全屏狀態(tài)設(shè)置按鈕的樣式名稱
this.setClassName_(this.button_, isFullScreen());
//為控件添加一個(gè)button的類型屬性
this.button_.setAttribute('type', 'button');
//為這個(gè)按鈕添加hint文字
this.button_.title = tipLabel;
//將按鈕文字標(biāo)簽添加到按鈕上
this.button_.appendChild(this.labelNode_);
//為HTML按鈕綁定點(diǎn)擊事件和回調(diào)函數(shù)
this.button_.addEventListener(EventType.CLICK, this.handleClick_.bind(this), false);
//構(gòu)造css類名
const cssClasses = this.cssClassName_ + ' ' + CLASS_UNSELECTABLE +
' ' + CLASS_CONTROL + ' ' +
(!isFullScreenSupported() ? CLASS_UNSUPPORTED : '');
//獲取本控件的DOM對(duì)象句柄,并將按鈕添加為這個(gè)DOM對(duì)象的子節(jié)點(diǎn)
const element = this.element;
element.className = cssClasses;
element.appendChild(this.button_);
//初始化另外兩個(gè)屬性
/**
* @private
* @type {boolean}
*/
this.keys_ = options.keys !== undefined ? options.keys : false;
/**
* @private
* @type {HTMLElement|string|undefined}
*/
this.source_ = options.source;
}
接下來是處理點(diǎn)擊事件的回調(diào)函數(shù):
/**
* @param {MouseEvent} event The event to handle
* @private
*/
handleClick_(event) {
event.preventDefault();
this.handleFullScreen_();
}
到這里,我們所需要的東西就基本上具備了,接下來就可以仿照寫一個(gè)自己的MyControl控件了。
import Control from 'ol/control/Control';
import {CLASS_CONTROL, CLASS_UNSELECTABLE, CLASS_UNSUPPORTED} from 'ol/css';
import {listen} from 'ol/events';
import EventType from 'ol/events/EventType';
class MyControl extends Control {
constructor(opt_options) {
const options = opt_options ? opt_options : {};
super({
element: document.createElement('div'),
target: options.target
});
this.cssClassName_ = options.className !== undefined ? options.className :
'ol-full-screen';
const label = options.label !== undefined ? options.label : '\u00f7';
this.labelNode_ = typeof label === 'string' ?
document.createTextNode(label) : label;
this.button_ = document.createElement('button');
const tipLabel = options.tipLabel ? options.tipLabel : '點(diǎn)我';
this.button_.setAttribute('type', 'button');
this.button_.title = tipLabel;
this.button_.appendChild(this.labelNode_);
listen(this.button_, EventType.CLICK,
this.handleClick_, this);
const element = this.element;
const cssClasses = this.cssClassName_ + ' ' + CLASS_UNSELECTABLE +
' ' + CLASS_CONTROL;
element.className = cssClasses;
element.appendChild(this.button_);
}
handleClick_(event) {
event.preventDefault();
alert('Your control is online!');
}
}
使用的時(shí)候,做如下調(diào)用(下面的代碼段省略了各種import):
let map = new Map({
controls: defaultControls().extend([
new MyControl()
]),
target: 'map',
layers: [
new TileLayer({
source: new XYZ({
url: 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png'
})
})
],
view: new View({
center: [0, 0],
zoom: 2
})
});
和原生風(fēng)格完全一樣,并且可重用性絕對(duì)OK。