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。