如何在Mapbox GL JS中注册点击事件时操纵事件冒泡

17

如何在图层点击事件中停止事件传播?

mapBox.on('click', layerId, function (e) {
    console.log(e);
    // e.stopPropagation(); which is not working 
    // e.originalEvent.stopPropagation(); which is not working 
    var popupHtml = getPopupHtmlWrapper(e.features[0]);
    new mapboxgl.Popup({closeButton:false})
        .setLngLat(e.lngLat)
        .setHTML(popupHtml)
        .addTo(mapBox);
});

你试图阻止事件冒泡到哪里? - mollymerp
@mollymerp 我已经尝试将cancelBubble属性设置为true,还有stopPropagation方法,但都没有成功(两者都是在我从回调中获取的e.originalEvent对象上执行)。 - Amit Gore
8个回答

13

Mapbox提供对原始事件的访问。这意味着每当我们想要将 click 限制为仅点击最上层时,我们只需要在第一层上调用 e.originalEvent.preventDefault() 并在下面的图层中检查 e.originalEvent.defaultPrevented

this.map
  .on('click', 'layer_1', function(e) { // topmost layer
    e.originalEvent.preventDefault();
    // layer_1 functionality
  })
  .on('click', 'layer_2', function(e) { // second topmost layer
    if(!e.originalEvent.defaultPrevented) {
      // click was performed outside of layer_1
      e.originalEvent.preventDefault();
      // layer_2 exclusive functionality
    }
  })
  .on('click', 'layer_3', function(e) {
    if(!e.originalEvent.defaultPrevented) {
      // click was performed outside of layer_1 and layer_2
      e.originalEvent.preventDefault();
      // layer_3 exclusive functionality here...
    }
...
  })

1
你实际上不需要为第一个事件做出不同的处理。e.originalEvent.defaultPrevented 默认为 false,直到你调用 e.originalEvent.preventDefault(),所以即使它是第一个事件监听器,if 语句内部也会执行。因此,你可以在循环中分配这些监听器,并对所有监听器都采用相同的方式进行处理。第一个不需要特殊处理。 - Sherri
这取决于您分配事件处理程序的确切顺序。如果您从两个不同的位置处理点击,则最后一个被分配的位置可以用于检查 defaultPrevented。否则它将始终为false,因为在另一个事件已经触发之后,您无法捕获是否正在阻止默认行为。 - RomanistHere

8
也许我们不需要全局变量,我遇到了与你相同的问题,并通过以下解决方案来解决它:

mapbox.on('click', 'layer1', function(e){  
   e.originalEvent.cancelBubble = true;
   /*
    //or you can add an attr
    e['bubble'] = 'no';
   
   */
   console.log('layer1');

}
mapbox.on('click', 'layer2', function(e){  
   if(e.originalEvent.cancelBubble){
     return;
   }
   /*
     if(e['bubble] && e['bubble'] == 'no'){
       return;
     }   
   */
   console.log('layer2');
}


为什么当你在函数体中使用mapbox.on('click', 'layer2', function(e){覆盖e时,if(e.originalEvent.cancelBubble){...会变成true呢? - four-eyes
仅供参考,根据MDN的说法,“Event接口的cancelBubble属性是对Event.stopPropagation()的历史别名”。(https://developer.mozilla.org/en-US/docs/Web/API/Event/cancelBubble) - Cristiano Dalbem

5

根据 @Stophface 的建议,我使用了

e => {
  const features = map.queryRenderedFeatures(e.point)
  if (features[0].layer.id === myLayerName ) {
    doMyClickAction(e)
  }
}

为了在此情况下只有第一次单击所关心的层时才对事件执行某些操作。

另一种稍微不同的选项是 - const features = map.queryRenderedFeatures(e.point, { layers: ['top-layer'] }) 如果没有features,就返回。 - Zach Smith

3

我希望找到相同的解决方案,但需要在地图和图层之间共享事件,因此,如果未单击图层,则希望调用地图单击侦听器,反之亦然,如果单击了图层,则希望跳过地图单击侦听器。

所以我最终得到以下代码:

// Event handler for layer
function(e) {
  e.preventDefault()
  // handle layer event
}

// Event handler for map
function(e) {
  if(e.defaultPrevented) return
  // handle map event
}

所以,防止默认行为是有效的,但您应该检查事件中的defaultPrevented属性并处理它。


这似乎是优雅和简单之间最好的平衡 :D - entio

2
一种方法是保存点击图层的事件坐标,然后将这些坐标与底层图层及其事件坐标进行比较。
let clickCoords = {};

this.map.on('click', 'points', event => {
  clickCoords = event.point;
  console.log('Layer click')
});

Now, detect a click on the map, not on the points layer

this.map.on('click', () => {
  // check if coords are different, if they are different, execute whatever you need
  if (clickCoords.x !== event.point.x && clickCoords.y !== event.point.y) {
    console.log('Basemap click');
    clickCoords = {};
  }
});

或者更好的方法是使用queryRenderedFeatures()函数。
this.map.on('click', event => {
   if (this.map.queryRenderedFeatures(event.point).filter(feature => feature.source === YOURLAYERNAME).length === 0) {
    console.log('Basemap click');
  }
});

这是一个Mapbox示例

0

我不知道有什么简单的方法来处理这个问题。我假设你正在尝试防止点击事件传播到其他层。你可以使用一个全局变量来存储刚刚发生在那些坐标上的点击事件。然后在你的其他点击处理程序中检查这种情况。

var recentClicks = {layer1 : null}

mapBox.on('click', "layer1", function (e) {
    console.log(e);
    recentClicks["layer1"] = e.lngLat
    // Proceed to creating pop-up
});

mapBox.on('click', "layer2", function (e) {
    console.log(e);
    if(recentClicks["layer1"] && recentClicks["layer1"][0] == e.lngLat[0] && recentClicks["layer1"][1] == e.lngLat[1]){
        recentClicks["layer1"] = null
        return
    } else {
      // Proceed to treating layer 2 click event
    }
});

经过一些努力,您可以将此应用于无限数量的图层。


0

0

我创建了一个插件,它允许你在层之间停止事件传播(支持immediatePropagation)。试试这个库


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接