javascriptleafletpopupvuejs3zooming

Leaflet error when zooming after closing popup


I already saw the same question here: StackOverflow But none of the answers helped or atleast I didn't understand it.

If I place a new marker on the map by clicking and then remove the marker by clicking again and then attempt to zoom, I get the error:

TypeError: Cannot read properties of null (reading '_latLngToNewLayerPoint')
    at NewClass._animateZoom

A more simple example of this happening is when I click a marker to see the popup. After seeing the popup, I close the popup and then try to zoom and I get errors on every zoom.

When I first close the popup, I get a warning saying that listener not found which is odd, because I close the popup by simply clicking on the map and I DO have a listener for when I click on the map.

here is the code:

            var mapLink = '<a href="https://www.esri.com/">Esri</a>';
            var wholink = 'i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community';
            var satelliteLayer = L.tileLayer(
                'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
                attribution: '&copy; ' + mapLink + ', ' + wholink,
                maxZoom: 18,
            });

            var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
                maxZoom: 19,
                attribution: '© OpenStreetMap'
            });
            var mapboxUrl = 'https://api.mapbox.com/styles/v1/johnmichel/ciobach7h0084b3nf482gfvvr/tiles/{z}/{x}/{y}?access_token=pk.eyJ1Ijoiam9obm1pY2hlbCIsImEiOiJjaW9iOW1vbHUwMGEzdnJseWNranhiMHpxIn0.leVOjMBazNl6v4h9MT7Glw';
            var mapboxAttribution = 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://mapbox.com">Mapbox</a>'
            var streets = L.tileLayer(mapboxUrl, { id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mapboxAttribution });

            var map = L.map("mapContainer", {
                center: [lat, lng],
                zoom: 10,
                layers: [osm, satelliteLayer, streets],
                maxZoom: 18,
                minZoom: 10,
                smoothWheelZoom: true,  // enable smooth zoom 
                smoothSensitivity: 1,   // zoom speed. default is 1
                smoothZoom: true,
                smoothZoomDelay: 1000 // Default to 1000
            }).on('click', this.onMapClick);
            map.on('moveend', this.onMapMoved)

            var baseMaps = {
                "OpenStreetMap": osm,
                "Satellite": satelliteLayer,
                "Mapbox Streets": streets
            };
            var kayakLaunchLayer = L.layerGroup([]);
            var yourPin = L.layerGroup([]);

            this.markerLayerGroup = L.layerGroup();
            this.singleMarkerLayerGroup = L.layerGroup();
            var layerControl = L.control.layers(baseMaps).addTo(map);
            map.addLayer(kayakLaunchLayer);
            map.addLayer(yourPin);
            this.markerLayerGroup = kayakLaunchLayer;
            this.singleMarkerLayerGroup = yourPin;


            // layerControl.addOverlay(this.markerLayerGroup, "Kayak Launches");
            // layerControl.addOverlay(this.singleMarkerLayerGroup, "Your Pin");
            map.invalidateSize();
            this.map = map;

this.map is a variable defined in data in my Vue component and so is this.singleMarkerLayerGroup and this.markerLayerGroup.

And this is the function where I actually add the markers to the this.markerLayerGroup so that I can see them as an overlay on the map:

var lat = launch.loc.coordinates[1];
var lng = launch.loc.coordinates[0];
var marker = L.marker([lat, lng]).addTo(this.markerLayerGroup).on('click', this.onMarkerClick);
var photoImgwithContent = `<h2><a href="/launch/${launch._id}">${launch.name}</a></h2><img src="${launch.images[0]}" height="150px" width="150px"/><h3></h3>${this.capitalizeFirstLetter(launch.waterType)}`;
marker.bindPopup(photoImgwithContent);

Now what is wrong with any of this? I have restructured the code to follow documentation as closely as possible and still stumped.

ERRORS

But strangely, if I turn off zoom animation by applying this map option: zoomAnimation:false, then the error is gone but the map look terrible without zoom animation.


Solution

  • This seems like a similar situation as in Uncaught TypeError: this._map is null (Vue.js 3, Leaflet), since you seem to use Vue.js 3, and the error occurs when zooming the map after some Layers/Popup are removed:

    the culprit is the proxying of this.map by Vue, which seems to interfere with Leaflet events (un)binding. It looks like Vue 3 now automatically performs deep proxying

    A solution consists in "unwrapping" / un-proxying the map and the Layer Groups (i.e. all Leaflet objects that you store in this Vue component data) whenever you use them, e.g. with Vue3's toRaw utility function:

    var marker = L.marker([lat, lng])
      .addTo(toRaw(this.markerLayerGroup));
    

    See also the Vue 3 guide to Reduce Reactivity Overhead for Large Immutable Structures.