Leaflet popup.update() Resizing Solution - Recreat

2019-05-26 09:19发布

问题:

I applied a popup.update() code snippet provided by @ghybs that works very well to adjust the popup to fit within the frame of the map, as you can see the code here:

document.querySelector(".leaflet-popup-pane").addEventListener("load", function (event) {
  var tagName = event.target.tagName,
      popup = map._popup;
  console.log("got load event from " + tagName);
  if (tagName === "IMG" && popup) {
    popup.update();
  }
}, true);

The problem is that I embedded a url in the thumbnail for each popup. When I go to click on the thumbnail, the cursor indicates that a 'click' should be possible, but it does not do anything. When I right click the thumbnail, I can open the url in a new tab. So the url is there, just not working the way I want it to. A friend and I looked at the code and we determined that the javascript popup is constantly being recreated. He suggested it might be the update call, but wasn't sure. If there is a way to stop the javascript from constantly recreating the popup, then that might solve the issue.

He also noted that when he stops javascript from running, the url is only clickable on the bottom half of the thumbnail (specifically 14px high) when it is supposed to occupy the entire thumbnail (which is typically 250px).

When I've checked for console.log(popup) right after the update, it gets stuck in an infinite loop. I'm guessing this is the heart of the problem. Is there a way to stop the update after it updates the popup size? I'm hoping this would free the embedded URL so as to be clickable, but I would also like the height of the link to match the entire thumbnail.

For reference, I am extracting the points from a geojson file and applying the same method to each point, like so:

var clusters = L.markerClusterGroup({maxClusterRadius:75});
var getjson = $.getJSON("map-v2.geojson",function(data){
  var bev = L.geoJson(data,{
    pointToLayer: function(feature,latlng){
      var marker = L.marker(latlng, { tags: feature.properties.Genres.concat(feature.properties.Creator)});
      marker.bindPopup('<p align=center>' + '<strong>Title: </strong>' + feature.properties.Title + '<br/><a href="' + feature.properties.Image_Bank_URL + '" target="_blank"><img src="' + feature.properties.Thumbnail_URL + '"/></a><br/>' + '<strong>Date: </strong>' + feature.properties.Date + '<br/>' + '<strong>Creator: </strong>' + feature.properties.Creator + feature.properties.Genre, {minWidth : 250});
      return marker;
    }
  });
  clusters.addLayer(bev);
  map.addLayer(clusters);
});

回答1:

Welcome to SO!

Hum indeed it looks like the given workaround does create an infinite loop when you specify the popup content as HTML string containing an <img>. What happens is that when an image completes loading, the popup.update() resets the Popup content using the HTML string, hence re-creates the <img> element, which emits a new "load" event, even if now it comes from browser cache. Then the listener executes popup.update() again, etc.

Demo (open your Web Console to see the inifinite loop logging "got load event from IMG"):

var map = L.map('map').setView([48.86, 2.35], 11);

// Modify the cache busting value to force browser fetching from network.
var imgSrc = 'https://a.tile.openstreetmap.org/0/0/0.png?bust=1';
var popupContent =
  '<a href="https://a.tile.openstreetmap.org/0/0/0.png" target="_blank">' +
  '<img src="' + imgSrc + '"/></a>';

L.marker([48.86, 2.35]).addTo(map).bindPopup(popupContent);

document.querySelector(".leaflet-popup-pane").addEventListener("load", function(event) {
  var tagName = event.target.tagName,
    popup = map._popup;
  console.log("got load event from " + tagName);
  if (tagName === "IMG" && popup) {
    popup.update();
  }
}, true);

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
html,
body,
#map {
  height: 100%;
  margin: 0;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.3/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin="" />
<script src="https://unpkg.com/leaflet@1.3.3/dist/leaflet-src.js" integrity="sha512-GosS1/T5Q7ZMS2cvsPm9klzqijS+dUz8zgSLnyP1iMRu6q360mvgZ4d1DmMRP2TDEEyCT6C16aB7Vj1yhGT1LA==" crossorigin=""></script>

<div id="map"></div>

In your very case, if you are sure there will be only 1 Popup open at any given time, and that it includes only a single <img>, you could simply set a flag at first "load" event on that Popup, in order to prevent the inifinite looping:

var map = L.map('map').setView([48.86, 2.35], 11);

// Modify the cache busting value to force browser fetching from network.
var imgSrc = 'https://a.tile.openstreetmap.org/0/0/0.png?bust=2';
var popupContent =
  '<a href="https://a.tile.openstreetmap.org/0/0/0.png" target="_blank">' +
  '<img src="' + imgSrc + '"/></a>';

L.marker([48.86, 2.35]).addTo(map).bindPopup(popupContent);

document.querySelector(".leaflet-popup-pane").addEventListener("load", function(event) {
  var tagName = event.target.tagName,
    popup = map._popup;
  console.log("got load event from " + tagName);
  // Also check if flag is already set.
  if (tagName === "IMG" && popup && !popup._updated) {
    popup._updated = true; // Set flag to prevent looping.
    popup.update();
  }
}, true);

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
html,
body,
#map {
  height: 100%;
  margin: 0;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.3/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin="" />
<script src="https://unpkg.com/leaflet@1.3.3/dist/leaflet-src.js" integrity="sha512-GosS1/T5Q7ZMS2cvsPm9klzqijS+dUz8zgSLnyP1iMRu6q360mvgZ4d1DmMRP2TDEEyCT6C16aB7Vj1yhGT1LA==" crossorigin=""></script>

<div id="map"></div>

(note that SO code snippets seem to prevent <a href> links to open, so here is a Plunk to check that the link does open normally: https://next.plnkr.co/edit/ore09Yxmm6DVmJGc)

Now to generalize the solution for the case where an arbitrary number of images are contained in the HTML string, and when multiple Popups can be open simultaneously, we could imagine:

  1. On "popupopen" and each image "load" events, check if all Popup images have a non zero naturalWidth, update otherwise.
  2. Store the reference to the Popup on each image, so that we do not have to resort to read the map._popup (which reference the last open Popup only).

var map = L.map('map', {
  closePopupOnClick: false
}).setView([48.86, 2.35], 11);

// Modify the cache busting value to force browser fetching from network.
var imgSrc1 = 'https://a.tile.openstreetmap.org/0/0/0.png?bust=3';
var imgSrc2 = 'https://a.tile.openstreetmap.org/11/1037/704.png?bust=3';
var popupContent =
  '<a href="' + imgSrc1 + '" target="_blank">' +
  '<img src="' + imgSrc1 + '"/></a>' +
  '<a href="' + imgSrc2 + '" target="_blank">' +
  '<img src="' + imgSrc2 + '"/></a>';

L.marker([48.86, 2.35]).addTo(map).bindPopup(popupContent, {
  autoClose: false
}).on('click', function() {
  // Open another Popup after this one.
  m2.openPopup();
});

var m2 = L.marker([48.86, 2.32]).bindPopup('Second Popup', {
  autoClose: false
}).addTo(map);

// Prepare the Popup when it opens.
map.on('popupopen', function(event) {
  var popup = event.popup;
  popup._imgAllSized = popupImgAllSized(popup);
});

document.querySelector(".leaflet-popup-pane").addEventListener("load", function(event) {
  var target = event.target,
    tagName = target.tagName,
    popup = target._popup;
  console.log("got load event from " + tagName);
  // Also check the Popup "_imgAllSized" flag.
  if (tagName === "IMG" && popup && !popup._imgAllSized) {
    console.log('updated');
    // Update the flag, in case all images have finished loading.
    popup.update();
    popup._imgAllSized = popupImgAllSized(popup);
  }
}, true);

function popupImgAllSized(popup) {
  // Get the HTMLElement holding the Popup content.
  var container = popup._contentNode;
  var imgs = container.querySelectorAll('img');
  var imgAllSized = true;
  for (var i = 0; i < imgs.length; i += 1) {
    // Store reference to popup in <img>
    imgs[i]._popup = popup;
    // Check if the image has unknown size.
    if (!imgs[i].naturalWidth) {
      imgAllSized = false;
    }
  }
  console.log(imgAllSized);
  return imgAllSized;
}

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
html,
body,
#map {
  height: 100%;
  margin: 0;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.3/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin="" />
<script src="https://unpkg.com/leaflet@1.3.3/dist/leaflet-src.js" integrity="sha512-GosS1/T5Q7ZMS2cvsPm9klzqijS+dUz8zgSLnyP1iMRu6q360mvgZ4d1DmMRP2TDEEyCT6C16aB7Vj1yhGT1LA==" crossorigin=""></script>

<div id="map"></div>

Then we could even further improve this solution by trying to update as soon as the images have their naturalWidth, instead of waiting for their "load" event, so that even while the browser is still fetching them, the Popup size and position is updated.