Robert J. Walker
Robert J. Walker

Reputation: 10363

Detect user-initiated pan/zoom operations on Leaflet

I have a Leaflet map which is used for location-sharing. When a user shares their location, a marker displaying their location is added to the map for all other users to see. It auto-fits the map to display all the markers whenever one is added, moved or deleted. I've also added a custom control that can toggle the auto-fit behavior on and off. This all works fine, but I would also like to make the map smart enough to automatically turn off the auto-fit behavior if the user pans or zooms the map.

This turns out to be pretty difficult because I can't find a good way to distinguish whether or not a pan/zoom operation is initiated by the user or by an auto-fit. I was initially listening for the panstart and zoomstart events, but these are also triggered by the auto-fit. I figured that I could set a flag to tell it not to turn off auto-fit when the zoom/pan is caused by auto-fitting. I check this flag first before turning off auto-fit in response to panstart and zoomstart, then clear it when panend and zoomend are received.

This appears to work fine until an auto-fit occurs which does not result in a pan or zoom. Let's suppose that we have a large auto-fitted cluster of markers, and one of the ones in the middle is removed. Since the bound box is unchanged, no pan or zoom is triggered, and therefore the flag telling it not to turn off auto-fit never gets cleared. The next time the user pans or zooms the map, it doesn't turn off auto-fit like it should because it thinks it's still in the middle of an auto-fit operation.

How do I make it so that I can reliably turn off auto-fitting when the user pans or zooms the map directly, but leave it on when it is panned or zoomed by other means?

Here is the relevant code:

var markers = [];        // Map markers are stored in this array.
var autoFit = true;      // Whether auto-fit is turned on
var lockAutoFit = false; // Temporarily lock auto-fit if true
var map;                 // Leaflet map object

function initMap() {
    // Leaflet map initialized here
    map.on('movestart zoomstart', function() {
        if (!lockAutoFit) {
            autoFit = false;
        }
    });
    map.on('moveend zoomend', function() {
        lockAutoFit = false;
    });
}

function toggleAutoFit() {
    autoFit = !autoFit;

    if (autoFit) {
        lockAutoFit = true;
        fitMap();
    }
}

function addOrUpdateMarker(marker, coords) {
    lockAutoFit = true;
    // do the marker update here
    fitMap();
}

function removeMarker(marker) {
    lockAutoFit = true;
    // remove the marker here
    fitMap();
}

// Pans and zooms the map so that all markers fit in the map view.
// Invoked whenever a marker is added, moved or deleted, or when
// the user turns on auto-fit.
function fitMap() {
  if (!autoFit || !markers.length) {
    return;
  }

  map.fitBounds(new L.featureGroup(markers).getBounds());
}

Upvotes: 26

Views: 6527

Answers (5)

pjanecze
pjanecze

Reputation: 3175

You can achieve that by using "mouseup" event like this (react)

useMapEvents({
  mouseup: (event) => {
    event.target.lastMouseUp = new Date().getTime();
  },
  moveend: (event) => {
    const isUserMove =
      event.target.lastMouseUp && new Date().getTime() - event.target.lastMouseUp < 500;
    if (isUserMove) {
      const map = event.target;
      const bounds = map.getBounds();
      onBoundsChange(bounds);
    }
  },
});

Upvotes: 0

Wayneio
Wayneio

Reputation: 3576

EDIT: This apparently has been removed and no longer works

This is an old question, but as I found it recently, I think it's still relevent enough that a solution is worthwhile giving.

I followed this solution: Leaflet User Triggered Events

which is to use:

map.on('dragstart', function (e) {
    if (e.hard) {
        // moved by bounds
    } else {
       // moved by drag/keyboard
    }
});

The undocumented e.hard part is the solution here; the comments speak for themselves.

Alternativly, you might want to use moveend instead of dragstart

Upvotes: 2

Heinrich Filter
Heinrich Filter

Reputation: 6158

I ended up setting a flag around my fitBounds and setView calls e.g.:

isProgramaticZoom = true
map.fitBounds(new L.featureGroup(markers).getBounds());
isProgramaticZoom = false

Then the code to turn off auto-fit:

map.on('zoomstart', function() {
  if (!isProgramaticZoom) {
    //turn off auto-fit
  }
})

map.on('dragstart', function() {
  //turn off auto-fit
})

Unfortunately, still not ideal but should do the trick

Upvotes: 11

Nighto
Nighto

Reputation: 4202

The Leaflet reference states that it has zoomstart, zoomend and zoomlevelschange events.

Upvotes: -2

lightwaver
lightwaver

Reputation: 41

You could use drag, dragstart, dragend - at least for the map panning. For map zooming there isn't such an equivalent I think.

Upvotes: 1

Related Questions