简体   繁体   中英

How to manipulate event bubbling when registering click event upon a layer in mapbox gl js

How can I stop event propagation on layer click events?

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);
});

Mapbox provides access to the original event. Which means whenever we want to limit a click to the topmost layer clicked only, all we need to do is call e.originalEvent.preventDefault() on the first layer and check for e.originalEvent.defaultPrevented in the layers beneath:

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...
    }
...
  })

Maybe we don't need a global variable, I got the same problems as you, and I fixed it with the solution:

 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'); }

One approach is to save the event coordinates of the click on the layer and then compare these coordinates with the underlying layer and its event coordinates.

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 = {};
  }
});

Or, even better, use queryRenderedFeatures() .

this.map.on('click', event => {
   if (this.map.queryRenderedFeatures(event.point).filter(feature => feature.source === YOURLAYERNAME).length === 0) {
    console.log('Basemap click');
  }
});

Here is a Mapbox example .

Was looking to find the same solution, but sharing event between the map and layer, so if layer, for example, was not clicked, I would like map click listener to be invoked, and opposite, if layer was clicked, I would like map click listener to be skipped

So I've ended up with the folowing:

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

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

So, prevent default is working but you should check for defaultPrevented property in event and handle it.

I am not aware of any simple way of handling this. I am assuming you are trying to prevent the click event from propagating to an other layer. What you can do is using a global variable storing that a click just happened at those coordinates. Then in your other click handler you check this case.

 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 } });

With a bit of work, you can apply this to an infinite number of layers.

Per the suggestion by @Stophface, I used

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

so as to only do something with the event when the layer I care about in this instance is the first one that is clicked.

Some of the above suggestions worked after I adjusted my layer ordering by setting the beforeLayer parameter when adding a layer:

https://docs.mapbox.com/mapbox-gl-js/example/geojson-layer-in-stack/

If none of the above suggestions work for you, try playing with the layer order and going through them again.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM