JSAPI-在地圖上添加自定義覆蓋物

地圖上的覆蓋物

在地圖上添加覆蓋物有兩種方式,一是在canvas畫布上渲染,好比JSAPI GL繪製MultiMarker/MultiPolygon等矢量圖形覆蓋物就是經過編寫對應圖形的數據解析及渲染程序,直接繪製在底圖上層。這樣的渲染方式下視角變換時圖形也能夠實現3D形變。另外一種方式是經過CSS佈局將其餘DOM元素疊加到地圖容器之上,這種方式下視角變換時DOM元素需從新計算佈局,好比JSAPI v2的Marker/Polygon等覆蓋物,以及JSAPI GL的InfoWindow信息窗,這些都屬於DOM覆蓋物。css

地圖覆蓋物的兩種形式

若是你須要疊加一個自定義的複雜元素,第一種方式的話須要實現對應的數據解析和着色器程序,須要瞭解WebGL的渲染原理,成本很高,且不易變通。而DOM是每一個前端工程師都很是熟悉的,簡單幾個標籤加CSS就能實現高度定製的DOM元素。可是如何將一個DOM元素正確地安置在地圖上,而且隨着地圖平移、旋轉、縮放實時調整本身的位置呢?html

這就要使用到DOMOverlay了。它並非一個具體的DOM覆蓋物,而是全部DOM覆蓋物的抽象基類,InfoWindow就繼承自它。DOMOverlay抽象出了DOM覆蓋物的生命週期,公共屬性及方法,實現了地圖事件的監聽綁定及解綁,你只須要關注DOM節點的建立和位置計算方法便可。前端

DOMOverlay 接口設計

先來看看DOMOverlay的類關係圖,這裏結合了官網示例DOMOverlay中定義的Donut類做爲DOMOverlay的實現:node

DOMOverlay-UML圖

公共屬性及方法

事件監聽及觸發

從上圖可見,DOMOverlay繼承自Node.js的EventEmitter類,因此它已經實現了事件監聽、觸發等功能的封裝,不太熟悉的同窗能夠看看Node.js EventEmitter | 菜鳥教程web

地圖綁定與解綁

DOMOverlay有一個公共屬性map,其值爲該覆蓋物綁定的地圖實例,同時提供了setMap(map: Map)getMap()方法做爲map參數的訪問器。 要將自定義覆蓋物顯示在地圖上,首先得明確具體的地圖實例,有兩種辦法,一是在初始化參數中定義map屬性,二是經過setMap進行動態設置,能夠綁定到另外一個地圖實例上,或者解綁。 setMap作了什麼呢?綁定時一方面主要是將createDOM()返回的DOM元素加入到特定的節點下,使其覆蓋在地圖上方且能夠進行相對定位;另外一方面是監聽地圖變換執行updateDOM(),使DOM元素能夠跟隨地圖更新定位或內容。解綁時則是將其從父節點下去除,同時刪除對地圖事件的監聽。canvas

DOM元素

DOMOverlay的公共屬性dom指向的是該覆蓋物的具體元素,能夠是HTMLElement或者SVGElement,該元素的建立由子類進行實現,綁定地圖後會掛載到覆蓋在canvas畫布上層的一個div容器中。segmentfault

銷燬

當覆蓋物再也不被使用時應適時進行銷燬操做,以防內存泄漏。destroy方法封裝了銷燬時應執行的操做,一方面將地圖解綁,另外一方面刪除對象上註冊的全部監聽器。bash

抽象方法

DOMOverlay提供了4個抽象方法,在生命週期的不一樣階段進行調用。前端工程師

  • onInit在初始化階段調用,並透傳了構造函數的參數options,用於參數注入
  • createDOM在初始階段調用,用於建立DOM元素並將其返回,做爲dom屬性的值,並加入到特定的父節點下
  • updateDOM在地圖發平生移、縮放、旋轉時調用,用於更新DOM元素定位
  • onDestroy在銷燬階段調用,可在此函數中對自定義的對象和事件監聽進行刪除

具體的生命週期以下: app

DOMOverlay-生命週期

基於DOMOverlay實現自定義覆蓋物

舉個🌰:自定義環形餅圖

自定義覆蓋物

以官網示例中的Donut爲例,建立自定義環形餅圖。官網示例中使用了原生JS語法實現繼承,這裏咱們改用ES6語法實現下:

const SVG_NS = 'http://www.w3.org/2000/svg';

// 自定義環狀餅圖 - 繼承DOMOverlay
class Donut extends TMap.DOMOverlay {
    constructor(options) {
        super(options);
    }

    // 初始化:獲取配置參數
    onInit({
        position,
        data,
        minRadius = 0,
        maxRadius = 50,
    } = {}) {
        Object.assign(this, {
            position,
            data,
            minRadius,
            maxRadius,
        });
    }

    // 建立DOM元素,返回一個Element,使用this.dom能夠獲取到這個元素
    createDOM() {
        let svg = document.createElementNS(SVG_NS, 'svg');
        svg.setAttribute('version', '1.1');
        svg.setAttribute('baseProfile', 'full');

        let r = this.maxRadius;
        svg.setAttribute('viewBox', [-r, -r, r * 2, r * 2].join(' '));
        svg.setAttribute('width', r * 2);
        svg.setAttribute('height', r * 2);
        svg.style.cssText = 'position:absolute;top:0px;left:0px;';

        let donut = createDonut(this.data, this.minRadius, this.maxRadius);
        svg.appendChild(donut);

        return svg;
    }

    // 更新DOM元素,在地圖移動/縮放後執行
    updateDOM() {
        if (!this.map) {
            return;
        }

        // 經緯度座標轉容器像素座標
        let pixel = this.map.projectToContainer(this.position);

        // 使餅圖中心點對齊經緯度座標點
        let left = pixel.getX() - this.dom.clientWidth / 2 + 'px';
        let top = pixel.getY() - this.dom.clientHeight / 2 + 'px';
        this.dom.style.transform = `translate(${left}, ${top})`;
    }

    // 銷燬時
    onDestroy() {}
}
複製代碼

其中createDonut是根據數據和半徑建立對應的SVG圖形,這裏先不過多關注。

如何進行元素定位?

這裏重點說明下updateDOM的實現,如何進行定位更新。 首先,咱們在初始化階段給position屬性賦值,position是一個經緯度對象,能夠經過map.projectToContainer方法轉爲地圖容器內的像素座標,記爲pixel。地圖容器座標系是以地圖容器左上角爲原點,向右爲x正方向,向下爲y正方向的座標系。 另外,咱們在createDOM方法中對生成的svg元素設置了CSS樣式position:absolute;top:0px;left:0px;,因此元素實際定位是與地圖容器左上角對齊。 咱們須要讓環形餅圖的中心與pixel位置對齊,首先能夠經過clientWidth/clientHeight獲取元素寬高,而後計算獲得元素左上角的像素座標爲(lefttop),最後經過transform: translate(${left}, ${top})設置平移偏移量,將元素移動到對應位置。

爲何不使用top: ${top}; left: ${left}進行定位呢? 由於transformtop/left性能好不少。top/left是在CPU上進行計算,會引發周圍區域的重繪;而transform是利用GPU計算能力,且是在獨立的圖層中進行變換,不會引發重繪。具體能夠參考建立前端平移動畫爲什麼 translate() 優於 top/right/bottom/left

如何實現click監聽?

有的同窗發現建立了自定義覆蓋物以後就不能像MultiMarker那樣經過on('click')監聽到點擊事件了,這是爲何呢?由於你沒有觸發事件啊:joy: 首先你須要監聽DOM元素的點擊事件,能夠在createDOM中實現:

// 建立DOM元素,返回一個Element,使用this.dom能夠獲取到這個元素
    createDOM() {
        ...

        // click事件回調
        this.onClick = () => {
            // DOMOverlay繼承自EventEmitter,可使用emit觸發事件
            this.emit('click');
        };
        
        // 使用addEventListener實現DOM元素的click監聽
        svg.addEventListener('click', this.onClick);
        return svg;
    }
複製代碼

click事件回調中能夠直接執行你想要的操做,或者調用emit觸發事件,就能夠觸發經過on掛載的監聽器了,以下:

let donut = new Donut({
    map,
    position: new TMap.LatLng(40.02906301748584, 116.25499991104516),
    data: [18, 41, 50],
    minRadius: 20,
    maxRadius: 28
})
donut.on('click', () => {
    console.log(`環形圖被點擊,位置爲${donut.position}`);
});
複製代碼

須要注意的是,在銷燬時應該將事件監聽刪除,因此onDestroy應相應修改成:

// 銷燬時需解綁事件監聽
    onDestroy() {
        if (this.onClick) {
            this.dom.removeEventListener(this.onClick);
        }
    }
複製代碼

相似的,你能夠監聽mousedownmouseup以及移動端的touchstarttouchend等事件,由於是自定義元素,因此控制權在你本身手上哦。

爲何出現偏移?

有的同窗在實現自定義覆蓋物以後,發現建立多個元素會發生向下偏移,且逐個的偏移量愈來愈多,這是爲何? 或許你能夠檢查下DOM元素是否是沒有設置position:absolute;top:0px;left:0px;,若是沒有設置絕對定位以及座標爲(0, 0)的話,則transform是在元素本來的定位上進行偏移,且元素沒有脫離文檔流,後加入的元素會依次下移。

其餘應用

DOMOverlay能夠應用在各類圖文結合、不易繪製的元素上。 好比使用點聚合接口時,若是想要使用自定義樣式,並且須要顯示簇大小,就可使用自定義DOM元素來表達聚合簇。

點聚合自定義樣式

再好比編輯器中,繪製和編輯圖形時圖形須要實時變化,使用矢量圖形圖層須要不斷重構數據,有較大開銷,因此也是結合DOM覆蓋物,經過SVG渲染單個圖形。

可編輯圖形

另外,有的同窗還問到,JSAPI v2中的marker跳動動畫在GL裏怎麼實現呢?其實也可使用自定義覆蓋物來實現,官網也提供了marker動畫示例

什麼狀況下不適合使用DOMOverlay 須要注意的是,當你須要繪製大量(>1000)的覆蓋物時是不適合使用DOMOverlay的,由於每一個DOM元素都是單獨進行定位更新的計算,會帶來很是大的開銷,在地圖變化時會很是卡頓。 海量覆蓋物的渲染仍是推薦使用MultiMarker/MultiPolygon等矢量圖形圖層,或者位置數據可視化API,提供了散點圖、弧線圖、軌跡圖、區域圖等可視化類型。

相關文章
相關標籤/搜索