Skip to content

Instantly share code, notes, and snippets.

@perliedman
Created March 7, 2017 15:11
Show Gist options
  • Save perliedman/84ce01954a1a43252d1b917ec925b3dd to your computer and use it in GitHub Desktop.
Save perliedman/84ce01954a1a43252d1b917ec925b3dd to your computer and use it in GitHub Desktop.
Click through multiple layers of Leaflet VectorGrid
// Since overlays are VectorGrid layers with canvas rendering,
// they don't support clicking through them (the topmost canvas
// swallows the event, lower layers will not see it).
// We workaround this by this hack (inspired by
// http://www.vinylfox.com/forwarding-mouse-events-through-layers/):
//
// All overlays are in their own Leaflet pane. When a click hits a
// layer in the pane, we first handle the event like normal, and then
// hit the event handler below this comment.
//
// The event handler will hide the original target layer's DOM element,
// and then find out which DOM element is under it, and re-dispatch the same
// event on that element, and continuing this process until all layers
// have been dispatched, or abort if a layer's event handler stops the
// event.
//
// We currently only do this for clicks, since hiding and redisplaying
// elements is a performance hog if you do it in for example mousemove.
overlayPane = map.createPane('my-overlays');
L.DomEvent.on(overlayPane, 'click', function(e) {
if (e._stopped) { return; }
var target = e.target;
var stopped;
var removed;
var ev = new MouseEvent(e.type, e)
removed = {node: target, display: target.style.display};
target.style.display = 'none';
target = document.elementFromPoint(e.clientX, e.clientY);
if (target && target !== overlayPane) {
stopped = !target.dispatchEvent(ev);
if (stopped || ev._stopped) {
L.DomEvent.stop(e);
}
}
removed.node.style.display = removed.display;
});
@johnclittle
Copy link

Is there any benefit to using target.style.pointerEvents = 'none' over target.style.display = 'none' ?

@mngyng
Copy link

mngyng commented Feb 25, 2022

The click works OK but I'd need to set the popup to stay by L.map('map', { ... closePopupOnClick: false }. Otherwise the popup shows up in a flash then gone.

@mngyng
Copy link

mngyng commented Mar 4, 2022

I found the cursor not functioning properly as having multiple layers should do: the cursor changes to <pointer>(the "finger") only at the top vector tiles. Here's the workaround I did:

For the event dispatching, dispatch also the mousemove (instead of mouseover) event, additional to the click event:

function enactDispatch(paneObj){
	L.DomEvent.on(paneObj, 'mousemove click', function(e) {
		if (e._stopped) { return; }
	
		var target = e.target;
		var stopped;
		var removed;
		var ev = new MouseEvent(e.type, e)
	
		removed = {node: target, display: target.style.display};
		target.style.display = 'none';
		target = document.elementFromPoint(e.clientX, e.clientY);
		target.dispatchEvent(ev)
	
		L.DomEvent.stop(e);
	
		removed.node.style.display = removed.display;
	});
}

vecLayer1 = map.createPane('veclayer1');
enactDispatch(vecLayer1);

vecLayer2 = map.createPane('veclayer2');
enactDispatch(vecLayer2);

Then when adding the vector tile layers, rebuild the mouse cursor change by assigning$('.leaflet-tile-loaded') properties on mouseover and mouseout events.

function vectorGrid(veclayerpath,paneName){
	var vgrd = L.vectorGrid.protobuf(veclayerpath+"{z}/{x}/{y}.mvt", {
		rendererFactory: L.canvas.tile,
		vectorTileLayerStyles: {
			<<the styles>>
		},
		interactive: true,
		getFeatureId: function(f) {
			return f.properties.id;
		},
		pane: paneName
	})
	.on('click', function(e) {
		<<the click response>>
	})
	.on('mouseover', function(e) {
		$('.leaflet-tile-loaded').addClass("leaflet-interactive");
	})
	.on('mouseout', function(e) {
		$('.leaflet-tile-loaded').removeClass("leaflet-interactive");
	})
	return vgrd
}

vectorGrid(<<path_to_backend_of_veclayer1>>,'veclayer1').addTo(map);
vectorGrid(<<path_to_backend_of_veclayer2>>,'veclayer2').addTo(map);

Hope you find this helpful.

@mngyng
Copy link

mngyng commented Mar 23, 2022

Also, in case the above I said is implemented, click events are somehow disabled when adding GeoJSON layers to their panes.
I used mousedown instead as a workaround.

Also, if the web map is also for use of mobile devices, you might have already used leaflet-touch-helper.js to make line features easier to click. In that case, you'll also need to add mousedown event to the listeners in leaflet-touch-helper.js, otherwise the extraWeight-clickables will not work.

Unlike vector tile layers though, mouseover and mouseout don't need to be reconstructed.

@fede1608
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment