在總結方法之前,先總結一下問題。
用過leaflet的人都知道,leaflet中有這樣一個方法:
L.geoJSON(data, { style: function (feature) { return {color: feature.properties.color}; } }).bindPopup(function (layer) { return layer.feature.properties.description; }).addTo(map);
這個方法可以將封裝好的GeoJson直接轉化為FeatureGroup,非常方便。
GeoJson 的 featureType 有多種,這里拿 Point 和 Polygon 兩類來舉例說明,它們在 leaflet 中是兩個代表。Point 類型的 geojson 數據在轉化為地圖圖層的渲染方式是 marker,本質上是 div,也就是說,如果你的 geojson 里有 1 萬條數據,那它就會在地圖上創建 1 萬個 div,很恐怖有木有,事實上也是不可取,因為一口氣創建一萬個 div 還是帶來了很大的工作量,一般配置的電腦上運行這樣的頁面會卡出翔;再說 Polygon 多邊形類型,Polygon 類型的 geojson 數據在轉化為地圖圖層的渲染方式是 Canvas,這與 marker 有本質上的區別,即使你的 geojson 里有 1 萬條數據,它也只會在其所在的 Pane 上創建 1 個 Canvas標簽,頁面地圖流暢程度絲毫不受影響。
說到這里,其實離本文的問題已經跑遠了,但說這些也是為了更好的理解這個問題或者說現象。div 是有層級關系(zIndex)的,而且這種關系是非常容易被控制的,任何時候我們都可以很輕易去改變,那么 Canvas 呢?
在 leaflet 的使用中可能會碰到這樣的情況,在同一層的 Pane 上,比如在 “ overlayPane ” 上,同時定義了以三角形為主和以四邊形為主的 Polygon 類型的 FeatureGroup,因此最后渲染出的頁面應該是這兩個 Feature Group 的內容展示在同一個 Canvas 標簽中,假如它們分別都綁定了各自的popup事件,而恰好它們在地圖上的區域有所交叉和重疊(假設三角形在四邊形的上方,即三角形覆蓋了四邊形的重疊區域),那這下子就尷尬了,當點擊地圖上它們的重疊區域時,這個問題的高潮也就來了。
從理論上無非四種結果:觸發三角形的popup;觸發四邊形的popup;二者都沒觸發;二者都觸發了。
而從測試后的事實的表象來看,無論是哪兒的重疊區域,被觸發的,永遠是下方被覆蓋的四邊形。
一開始我是懵比的,我覺得這是一個詭異的bug,一個問題的發生找不到合乎邏輯的解釋。寥寥數行代碼我反復看的心疲力乏,有一句話最能描述我當時的心境,理智讓我懷疑一切!
好了,不兜圈子了,這個bug神秘的面紗下,其實就是事件穿透,事實的本質是最后一種預想,二者都觸發了。leaflet 中 popup 有個特性,整個地圖中有且只能有一個 popup 會被觸發,事情的真相是,當鼠標點擊它們的重疊區域時,首先會觸發位於上方的三角形,緊接着觸發下方的四邊形。觸發四邊形的過程中會解除掉三角形的觸發狀態。這就是真相,一個典型的事件穿透問題。
搞清楚了問題的來龍去脈,解決問題的思路就一下打開了,可能稍微棘手點的就是,popup事件都是綁定在同一個canvas中的每一個單元上。
直接上代碼,后面不想再多說了,還是要留下一些思考的空間。
var level_active = false; var ship_active = false;
/*=============================================== (實時)通航等級 ====================================================*/ function getPassLevelLayer() { if (grade_cache == "") { return; } levelLayer = L.geoJson(grade_cache, { style: function (feature) { return {color: feature.properties.color, weight: weightValueArray[map.getZoom()], pane: 'overlayPane'}; }, onEachFeature: function (feature, layer) { layer.bindPopup(eachFeaturePopup(feature)); layer.on("mouseover",function(){ level_active = true; }).on("mouseout",function () { level_active = false; }).on('click',function () { if(level_active&&ship_active){ ship_popup_obj.openPopup(); } }); } }); return levelLayer; }
/*===================================================== 船 舶 ========================================================*/ function loadShipSoapLayer() { if (shipSoapLayer == null) { shipSoapLayer = L.geoJson(ship_soap_cache, { style: function (feature) { return { fillColor: feature.properties.Color, fillOpacity: 1, color: "black", weight: 1, pane: 'overlayPane' }; }, onEachFeature: function (feature, layer) { layer.bindPopup(eachShipFeaturePopup(feature), { closeButton: false, className: "ShipSoap", Ship_Id: feature.properties.ShipId }).on('popupopen ', function (e) { if(level_active&&ship_active){ ship_popup_obj = e.target; } var shipid = map._popup.options.Ship_Id; $('.ShipSoap-Foot_Btn').on('click', function () { window.layer.open({ type: 2, skin: 'layui-layer-hei', title: '通航查詢', shadeClose: false, shade: [0.9, '#6b6b6b'], maxmin: false, area: ['100%', '100%'], content: 'ship.html&shipId=' + shipid }); }) }).on('popupclose', function () { $('.ShipSoap-Foot_Btn').unbind(); }).on("mouseover",function(){ ship_active = true; }).on("mouseout",function () { ship_active = false; }); } }).setZIndex('9999'); } return shipSoapLayer; }