dynamic image dimensions #741

Open
digitalkaoz opened this Issue Jan 27, 2015 · 30 comments

Projects

None yet
@digitalkaoz

as you said in the docs its not possible, i even tried and went half way through...just one tiny piece missing. here my attempt:

        var images = [
              { src: "http://foo.com/bar.gif", w:0, h:0}
        ];
        var pswpElement = document.querySelectorAll('.pswp')[0];

        var options = {
            index: 0,
            showHideOpacity: true
        };

        var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, images, options);

        gallery.listen('imageLoadComplete', function(index, item) {
            var img = new Image();

            item.w = img.width;
            item.h = img.height;
        });

        gallery.init();

sadly it only works good for the lazyloaded images "left" and "right" but not for the current one requested.
in my use case i cant encode the size into the filename as the images are thumbnailed to fit in a box (e.g. image 760+420 should fit in a box of 400x250) and i dont know the resulting dimension, i even dont care ;).
i dont want to make backend requests to get the size, thats not performant. as the example above is half way working i think its possible by PhotoSwipe itself (and it should be a feature anyway ;) )

i think the easiest fix would be to start all displaying stuff after the event is done?

@dimsemenov
Owner

Related section in FAQ.

In documentation there is an example on how to add or edit slides dynamically, see http://photoswipe.com/documentation/api.html (at the bottom). You may preload image, retrieve its size, and append (or replace) the dummy image.

Or if you have just 1 or 2 images, you can preload it, and only then initialize PhotoSwipe.

For now there are no plans to add such feature in core, as PhotoSwipe displays and animates image before it's loaded.

@lucretiu

Is there any safe way to do this, so that the user sees no clipping or the image missing? I tried to implement it too, but replacing the dummy image works only when the image was previously loaded and the user hasn't reached that slide.
If the user scrolls fast through the photos, PhotoSwipe will not have time to replace the current slide's width and height, because the preloaded image didn't load yet.
Is there any way to reload the current slide? I just need to change the width and height properties of the current item in the items array.
I have been banging my head against the wall to find a solution for this for hours and hours and hours on end...

@vralle
vralle commented Jul 10, 2015

May be small questions about code example.
What happened with the memory usage after nn* clicks and nn* new images in memory?
Whether to reset the variable 'img = null;'?

@Juni4567

I solved the issue by adding the following css at the end of my stylesheet

.pswp__img {
  height: auto !important;
}

and then in the js files where it has the following code I asked it to calculate the width and height of the window and fit our image inside.

for(var i = 0; i < numNodes; i++) {

            figureEl = thumbElements[i]; // <figure> element

            // include only element nodes
            if(figureEl.nodeType !== 1) {
                continue;
            }

            linkEl = figureEl.children[0]; // <a> element

            //Instead of asking for height and width please calculate it for us We are lazy ;) Juni

            var j_width = $(window).width();
            var j_height = $(window).height();
            size = [j_width, j_height]

            // create slide object
            item = {
                src: linkEl.getAttribute('href'),
                w: parseInt(size[0], 10),
                h: parseInt(size[1], 10)
            };



            if(figureEl.children.length > 1) {
                // <figcaption> content
                item.title = figureEl.children[1].innerHTML;
            }

            if(linkEl.children.length > 0) {
                // <img> thumbnail element, retrieving thumbnail url
                item.msrc = linkEl.children[0].getAttribute('src');
            }

            item.el = figureEl; // save link to element for getThumbBoundsFn

            items.push(item);
        }

Be Careful I am using jQuery selector because I am too lazy. If you are not using jQuery please find a javascript way to calculate the height and width of your window!

@denjones
denjones commented Mar 1, 2016

I am using PhotoSwipe in my hexo theme with or without predefined dimension for photos. Here is my approach:

    // Pass data to PhotoSwipe and initialize it
    gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options);

    gallery.listen('imageLoadComplete', function(index, item) {
      var linkEl = item.el.children[0];
      var img = item.container.children[0];
      if (!linkEl.getAttribute('data-size')) {
        linkEl.setAttribute('data-size', img.naturalWidth + 'x' + img.naturalHeight);
        item.w = img.naturalWidth;
        item.h = img.naturalHeight;
        gallery.invalidateCurrItems();
        gallery.updateSize(true);
      }
    });

    gallery.init();
@redefinered

You can easily do this by enclosing the code inside a function and load the dimensions from data attributes when photoswipe is called into view like so:

galleryTrigger = $('.pswp-call');

    openPhotoSwipe = function( img, index ) {

        var pswpElement = document.querySelectorAll('.pswp')[0];

        // build items array

        var items = [];

        x = 0;
        galleryTrigger.each(function(){
            items.push({
                src     : $(this).data('url'),
                w       : $(this).data('width'),
                h       : $(this).data('height'),
                title   : $(this).data('caption'),
            });   
            x++;
        })

        // define options if needed
        var options = {
            index               : index,
            showHideOpacity     : true,

            // buttons
            closeEl             : true,
            captionEl           : true,
            fullscreenEl        : true,
            zoomEl              : true,
            shareEl             : true,
            counterEl           : true,
            arrowEl             : true,
            preloaderEl         : true,
        }

        var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options);

        gallery.init();

    }

    galleryTrigger.click(function(){

        var img = $(this).data('url');
        var index = $(this).data('index');

        openPhotoSwipe( img, index );

    });
@a-komarev

@denjones thanks for the sharing solution. But naturalWidth and naturalHeight are undefined in my case.

I've updated your solution a bit and added an ability to define data-size="auto" for verbosity.

gallery.listen('imageLoadComplete', function (index, item) {
    var linkEl = item.el.children[0];

    if (!linkEl.getAttribute('data-size') || linkEl.getAttribute('data-size') == 'auto') {
        var img = new Image();
        img.src = linkEl.getAttribute('href');

        linkEl.setAttribute('data-size', img.naturalWidth + 'x' + img.naturalHeight);
        item.w = img.naturalWidth;
        item.h = img.naturalHeight;
        gallery.invalidateCurrItems();
        gallery.updateSize(true);
    }
});

I found 2 issues with this way:

  1. If gallery.invalidateCurrItems(); present - first click on image displaying it without animation.

// What bad could happen if it will be omitted?

  1. If animation is disabled - PhotoSwipe perform init and only after will fire imageLoadComplete event. Because of it exception will be thrown:

photoswipe.min.js:4 Uncaught TypeError: Cannot read property 'x' of undefined

With gettingData event situation it's more strange - because it's firing before init and sometimes after, different on each page refresh.

@denjones

@a-komarev maybe you should reference an existing img element rather then creating one dynamically.

@a-komarev

@denjones but I have only thumbnails on the page wrapped in <a> with href of original images.

@inigol
inigol commented Jul 5, 2016

The photogallery can be opened only one time in Android but works well in iOS, why? everyone can help me?

@isapir
isapir commented Jul 22, 2016

@denjones Are you sure that this is working for you? I can't get the <img> at that part of the code. Only after the imageLoadComplete event handler exits I see it there. What does your markup look like for each slide?

I will try @a-komarev 's approach.

@denjones
denjones commented Jul 22, 2016 edited

@TwentyOneSolutions It does work for me in my case. check this sample http://blog.sprabbit.com/hexo-theme-chan/2013/12/26/images/

@isapir
isapir commented Jul 22, 2016 edited

Yes, I saw it, but when I tried that technique with many (about 40) images, and with disabling the browser's cache, I was getting errors (though TBH I think that the errors were due to the fact that I was using a different markup with no <img> tags).

I ended up with the following solution:

first, I added a boolean variable to each slide specifying if doGetSlideDimensions is required, so if there is no data-size it is set to true.

then, I wrote a function to get the image dimensions for a given slide, like so:

function getSlideDimensions(slide) {

    if (!slide.doGetSlideDimensions)
        return;    // make sure we don't keep requesting the image if it doesn't exist etc.

    var img = new Image();

    $(img).on("error", function(evt){

        slide.doGetSlideDimensions = false;
    });

    $(img).on("load", function(evt){

        slide.doGetSlideDimensions = false;

        slide.w = img.naturalWidth;
        slide.h = img.naturalHeight;

        photoSwipe.invalidateCurrItems();
        photoSwipe.updateSize(true);
    });

    img.src = slide.src;
}

I'm using here a global variable named photoSwipe which points to the PhotoSwipe object, but it can be passed as a variable instead, or written as a function inside the initializer where there is a reference to that instance.

The last thing is to call that function from two event handlers, gettingData and imageLoadComplete:

photoSwipe.listen("gettingData", function(index, slide){

    if (slide.doGetSlideDimensions) {

        setTimeout(
            // use setTimeout so that it runs in the event loop
             function(){ getSlideDimensions(slide); }
            ,300
        );
    }
});

photoSwipe.listen("imageLoadComplete", function(index, slide){

    if (slide.doGetSlideDimensions)
        getSlideDimensions(slide);
});

You can see a working example at http://www.intelliflame.com/gallery.htm

@elbasan
elbasan commented Aug 8, 2016

Anyone found a fix for this for us who have thumbnails?

@isapir
isapir commented Aug 9, 2016

@elbasankadrija my solution works well. see above.

@Jogai
Jogai commented Aug 31, 2016

Can isapi's script behavior made default within photoswipe?

@karboom
karboom commented Sep 15, 2016

@isapi Your solution works for me, but img blinks when it loaded on Android and IOS

@danbo
danbo commented Nov 3, 2016 edited

I missed this thread while trying to figure out the dynamic sizing issue :(

Good news is I may be on to something, but I haven't done any work with thumbnails yet.

I tested in Firefox on my android and it works quite well. The odd time it gets confused a bit with sizing when I reorient on the fly but I'm willing to live with that for now

// note: photoswipe.js: don't forget to move "_shout('imageLoadComplete', index, item);" before "item.img = null;"
gallery.listen('imageLoadComplete', 
    function(index, item) {

        var actualImageWidth = item.img.naturalWidth;
        var actualImageHeight = item.img.naturalHeight;

        // recalculate the fit ratio based on actual image dimensions instead of those which would have been specified in image collection
        // adapted from photoswipe.js:2743
        var viewportSize = gallery.viewportSize;
        var availableX = viewportSize.x;
        var availableY = viewportSize.y - item.vGap.top - item.vGap.bottom;

        var hRatio = availableX / actualImageWidth;
        var vRatio = availableY / actualImageHeight;

        item.fitRatio = hRatio < vRatio ? hRatio : vRatio;

        item.w = Math.round(actualImageWidth * item.fitRatio),
        item.h = Math.round(actualImageHeight * item.fitRatio),

        gallery.updateSize();
});
  • A side effect is that images take up more of the screen (zoomed in a bit).
  • I've also increased my maxSpreadZoom to 3 to allow a bit more zooming.
  • I'm planning on serving images that are 800x600 or 1024x768.
@DanielHeath

I've figured out an approach that should work in all supported browsers. Instead of using an img tag to render images, use the following:

      <svg
        class="pswp__img"
        width="${item.w}"
        height="${item.h}"
        viewBox="0 0 100 100"
        xmlns="http://www.w3.org/2000/svg"
        xmlns:xlink="http://www.w3.org/1999/xlink"
      >
        <image
          xlink:href="${item.src}"
          x="0"
          y="0"
          height="100"
          width="100"
        />
      </svg>

This lets you specify the width/height you want the photoswipe container to have; if the image has different dimensions, its larger dimension will fill the container and the smaller one is centred.

SVG support is quite good - IE9+, android 3+ etc.

Have I missed something important or does this enable fluid sized images?

  • Progressive loading - may have to create an img tag and not display it to keep the 'is loaded' check
  • Stretched placeholder - SVG stretches images really nicely
  • Initial zoom-in transition - SVG has a fixed size and animates nicely
  • Panning / Zooming - SVG has a fixed size and animates nicely
  • Caption positioning: I'm not clear from reading the code how this is used, but a fixed size element should keep working anyways.
@piernik
piernik commented Jan 18, 2017

Any news on adding that feature to core?

@DanielHeath

It's not a small job and I certainly wouldn't expect @dimsemenov to do it for us.

I would be keen to hear whether a patch would be welcome or not before attempting it. If not, I might still do it on a fork, but would have to really think about it. @piernik / @isapir hit me up if you're thinking about taking it on yourself, happy to talk about it more.

@isapir
isapir commented Jan 18, 2017

@DanielHeath I have submitted a couple of PRs a while back but it doesn't seem like anyone accepts them:
#1178
#1179

@DanielHeath

It's looking like he's taking a well-deserved holiday (no facebook/twitter/github activity). Hopefully he's having a great time!

@dimsemenov
Owner

The feature to dynamically specify dimensions will unlikely be added to core. I specially do not remove PRs related to it, so people can use them as a start if needed. As stated in FAQ: it's recommended to find a way to retrieve and store dimensions, or use another gallery script that is more suitable for such task.

@isapir, The same goes for "moving" animation when switching slides. There is a performance issue when trying to move large images on a weak PCs, as well as UX issue - a large part of users dislike very large and long moving transition - they just want to view photos.

Though the "fading" transition is currently in development, as it's much more visually elegant and you can pre-render the next view for ~60ms, you can play with a prototype here http://codepen.io/dimsemenov/pen/zNNaNy .

Along with the feature that allows opening in a zoomed state & panning with mouse movement http://codepen.io/dimsemenov/pen/jrpwEZ

@piernik
piernik commented Jan 19, 2017 edited

@dimsemenov Why not making other version/branch that is more advanced (with calculating dimensions, better animations, even with defined gallery html) so it would be as easy as other lightboxes. If someone is performace geek he would use this library - everyone else new branch/library?

Your library is very good but very complicated to use right away...

@dimsemenov
Owner
dimsemenov commented Jan 19, 2017 edited

Why not making other version/branch that is more advanced (with calculating dimensions, better animations, even with defined gallery html)

I don't have resources to maintain two separate versions of the script.

Your library is very good but very complicated to use right away...

I agree with that, that's why there will be a module (or maybe a separate file) that would allow easier initialization directly from a list of thumbnails, take a look at what markup is required for this example - http://codepen.io/dimsemenov/pen/zNNaNy Though it's still in development.

@piernik
piernik commented Jan 19, 2017

Oh thats great. Will the new module has also embed html of gallery? Whe do You plan to release? Should I wait or search for other lightbox?

And why not adding official module for dynamic diamentions?

@isapir
isapir commented Jan 19, 2017

That's fine by me. I just don't want to create a PR that will be forever lost in GithubSpace.

Also, I've concluded that for my projects I will use @dimsemenov 's Royal Slider (http://dimsemenov.com/plugins/royal-slider/). Seems to be well worth its affordable price.

@DanielHeath

I'd love to see a feature comparison between the two; $WORK are generally OK to pay for stuff but I'd have to be able to write up why we should use the paid one over the free one.

@piernik
piernik commented Jan 23, 2017

@dimsemenov When You plan to release new version?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment