Skip to content

Instantly share code, notes, and snippets.

@perliedman
Created March 7, 2017 15:11
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • 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;
});
@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.

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