2

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 Answers1

3

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.

ghybs
  • 47,565
  • 6
  • 74
  • 99
  • The good news is this solution does break the loop, which allows the embedded url to be accessed. The bad news is the fix no longer adjusts the height as intended (it adjusts halfway up the popup). I checked the console log (I added one after the popup.update), and it appears that when I click on a point for the first time, the eventlistener loads the event, checks if updated, updates, loads the event again, then the flag stops the update from occurring. So, it's going through the right steps, but for whatever reason, the first update isn't adjusting for some. https://embed.plnkr.co/Ee8QYy/ – J. Jonah Jameson Aug 08 '18 at 16:02
  • Not sure exactly what you mean? Your Plunker works as expected for me in Chrome and Firefox. – ghybs Aug 08 '18 at 16:11
  • Huh, that is bizarre. Well, I'll upload the corrected html to our dev website and see how it behaves there. Perhaps it's just an issue with my text-editor browser I'm using. One thing I'm noticing is that if the point is too close to the top of the map, it won't adjust, but a little farther down, it works like a charm. I'll mark this as the solution, since it's probably a problem unique to my setup. Thank you so much! And thank you for providing a great and thorough answer! Nothing can beat that for my first post! – J. Jonah Jameson Aug 08 '18 at 16:30
  • If you dont want to listen to load events, specifying explicit width= height= for all images in popups can work as well. You can override width and height with css, and insert orginal dimensions of your image, so the browser knows them already before loading, and this way, knows the real dimensions of your image. – benzkji Jun 23 '23 at 12:36