PhotoSwipe: Serving images of unknown size - suggestion/solution

How to serve images with unknown size - my solution:

var items = [ 
  { src: 'http://........', w:0, h:0 },
  { src: 'http://........', w:0, h:0 }
];

var gallery = new PhotoSwipe( ..... );
gallery.listen('gettingData', function(index, item) {
        if (item.w < 1 || item.h < 1) { // unknown size
        var img = new Image(); 
        img.onload = function() { // will get size after load
        item.w = this.width; // set image width
        item.h = this.height; // set image height
           gallery.invalidateCurrItems(); // reinit Items
           gallery.updateSize(true); // reinit Items
        }
    img.src = item.src; // let's download image
    }
});

gallery.init();

About this issue

  • Original URL
  • State: open
  • Created 9 years ago
  • Reactions: 166
  • Comments: 31 (4 by maintainers)

Most upvoted comments

@mjau-mjau The reason for the multiple gettingData events (and the flickering it causes) is the call to invalidateCurrItems() inside @gincius code (great work btw! thanks a lot!), which is actually not necessary. That call invalidates all loaded items in the array (visible an preloaded); but we just need to refresh one item and, in fact, just calling updateSize(true) is enough:

pswpGallery.listen('gettingData', function (index, item) {
    if (item.w < 1 || item.h < 1) {
        var img = new Image();
        img.onload = function () {
            item.w = this.width;
            item.h = this.height;
            pswpGallery.updateSize(true);
        };
        img.src = item.src;
    }
});

Just by removing that function call, the flickering and redundant background calls to gettingData are gone.

I tried the item.onloading = true approach too, but I run into some kind of problem (can’t remember right now) so I had to dig deeper and came up with the above solution.

I’ve extensively tested it with single- and multiple-image galleries in Firefox, Chrome, IE11 and Edge desktop browsers, and Firefox (Android), Safari (iPhone / iPad) and Chrome (both systems) mobile browsers. Images of unknown size are correctly resized and displayed in all of them. Hope it helps.

You can use this to fix it… Give any value you want for h and w and add

.pswp img {
    max-width: none;
    object-fit: contain;
}

above css

Just a quick addition to @gincius code, which might solve the issue for @sk29110 also. I noticed that gettingData triggers continuously, likely while images are being loaded? This is what I see in console for a 3-slide object:

screen1

This also means that the img.onload will be triggered multiple times for the same images, causing unnecessary invalidateCurrItems() and updateSize(true) being triggered, which could cause havoc. The simple solution is to add a flag item.onloading:

// Make sure the slide is not html, and that the onLoad was not already triggered for this item
if(item.html === undefined && item.onloading === undefined && (item.w < 1 || item.h < 1)) {
  item.onloading = true;

Although the gettingData listener still triggers, the img.onload will only trigger once per image:

screen2

This is my solution:

Create a function that sets the pswp-width and pswp-height attributes to the anchors before initializing the PhotoSwipe gallery. We need to wait until all the images have been loaded, and we’re using promises for this.

async function prepareImagesForPhotoswipe() {
    // Get the <a> tags from the image gallery
    const imagesList = document.querySelectorAll(".image-masonry__item a");
    const promisesList = [];
    imagesList.forEach((element) => {
        const promise = new Promise(function (resolve) {
            let image = new Image();
            image.src = element.getAttribute('href');
            image.onload = () => {
                element.dataset.pswpWidth = image.width;
                element.dataset.pswpHeight = image.height;
                resolve(); // Resolve the promise only if the image has been loaded
            }
            image.onerror = () => { resolve(); };
        });
        promisesList.push(promise);
    });
    // Wait for all promises to be resolved
    await Promise.all(promisesList);
}

// Call the function using await
await prepareImagesForPhotoswipe();

// Now initialize the lightbox:
const lightbox = new PhotoSwipeLightbox({
    gallery: '.image-masonry',
    children: '.image-masonry__item a',
    showHideAnimationType: 'zoom',
    showHideOpacity: true, 
    showAnimationDuration: 200,
    pswpModule: photoswipe
});

lightbox.init();

A slightly modified version of the previous code to use the thumbnail image size (assuming a more or less equal image ratio) as a initial fallback:

Ps: my item has a el property denoting its relative HTMLElement

pswpGallery.listen('gettingData', (index, item) => {
  if (!item.w || !item.h) {
    const innerImgEl = item.el.getElementsByTagName('img')[0]
    if (innerImgEl) {
      item.w = innerImgEl.width
      item.h = innerImgEl.height
    }

    const img = new Image()
    img.onload = function () {
      item.w = this.width
      item.h = this.height
      pswpGallery.updateSize(true)
    }
    img.src = item.src
  }
})

Hello! I’m trying to implement this method on this codepen example without success :

https://codepen.io/alienlebarge/pen/Kdrxga

Can someone implement it?

@mjau-mjau Thanks for your quick reply! In the meantime, I fixed it by just preloading the images and setting width/height outside of the ‘gettingData’ event.

@mjau-mjau use the event ‘imageLoadComplete’ instead of ‘gettingData’ work for me.