Search code examples
jqueryajaximagerssflickr

Getting the height of an image with jQuery from Flickr without loading the image file?


I'm currently working on a jQuery solution where I want to load images from a Flickr RSS-feed. I'm not satisfied with the default size I get from the feed though - I want the images I load to be equal to or higher than the height of the wrapper element I'm displaying them in. (Width is not a problem here.)

According to their API documentation, Flickr has an image size system that looks like this:

s   small square 75x75
t   thumbnail, 100 on longest side
m   small, 240 on longest side
-   medium, 500 on longest side
z   medium 640, 640 on longest side
b   large, 1024 on longest side*
o   original image, either a jpg, gif or png, depending on source format

* Before May 25th 2010 large photos only exist for very large original images

This means that there is no guaranteed height - I need to check the height for each image I want to load. An example: if an image is a panorama and I load the large size, the height is probably still not enough to fill out my wrapper element. This is because the longest side will be the width and it will be 1024.

To complicate things, the height of the wrapper can vary - not dynamically after the page has loaded, but from page to page. On some pages it's 300 pixels, on others it's 100 pixels and so on.

As far as I understand the only way to get the height of an image file is to actually load it. So I have written some recursive code that loads an image, look's at its height and if it's not sufficient it calls itself again to load the next size. In the current version it starts with the smallest size and works its way up until it finds a suitable image or the biggest image possible.

Here's the code:

$.getJSON(ajaxContentURL, function(data) {
        var flickrImages = [];

        // An array of object with data pertaining Flickr's image sizes
        var flickrImageSizes = [{ size: "small square", pixels: 75, letter: "_s" }, 
                                { size: "thumbnail", pixels: 100, letter: "_t" }, 
                                { size: "small", pixels: 240, letter: "_m" }, 
                                { size: "medium", pixels: 500, letter: "" }, 
                                { size: "medium 640", pixels: 640, letter: "_z" }, 
                                { size: "large", pixels: 1024, letter: "_b"}];

        $.each(data.items, function(index, item) {
            flickrImages.push(loadFlickrImage(item, 0));
        });

        function loadFlickrImage(item, sizeIndex) {
            var path = item.media.m;
            var imgSrc = path.replace("_m", flickrImageSizes[sizeIndex].letter);
            var tempImg = $("<img />").attr("src", imgSrc);

            tempImg.load(function() {
               // Is it still smaller? Load next size
               if (this.height < el.data("scrollableAreaHeight")) {
                    // Load a bigger image, if possible
                    if ((sizeIndex + 1) < flickrImageSizes.length) {
                        loadFlickrImage(item, sizeIndex + 1);
                    } else {
                        return this;
                    }
               else {
                    return this;
               }
            });
        }
});

It's a litte rough around the edges, but I hope you get the picture.

This works, but it just seems so wasteful to have to load so many versions of an image just to get the right size?

Do you have any suggestions for improvements? I'm thinking that it should start by making a qualified guess at which could be the best size and load it first. Then it would see if it fits or if it's too small or too big and load the next or previous image on the size scale. That way you could reduce the amount of images to load just to get the right size.

Even better: is there a way to find out the image sizes without loading the actual image files? Remember that I don't have access to the complete Flickr API - I'm just accessing a JSON-feed, like this one.

I'd appreciate any feedback!

Thanks in advance!


Solution

  • I've solved this by first making a qualified guess as to which image size might be the best one. This guess is based on the height of the element inside which the loaded images will be put. Then I load this plausible image size to see if it's tall enough. If it's taller than or equal to the desired height, I use it. Otherwise I load the next bigger size, check its height and so on.

    Even though this probably means that I will be loading a larger amount of images compared to just loading the biggest size first I think that it's cheaper when it comes to the total amount of kilobytes loaded. Some preliminary tests show that from a standard feed of 20 Flickr images, 1 or 2 images need to be reloaded after the first load.

    Let me know if you put this solution to the test and end up with a different result or think than my approach is wrong. I'm looking for the cheapest and best solution.

    Here's the current code if anyone is interested:

    $.getJSON(flickrJsonUrl, function(data) {
        // small square - size is 75x75
        // thumbnail -> large - size is the longest side
        var flickrImageSizes = [{ size: "small square", pixels: 75, letter: "_s" },
                                { size: "thumbnail", pixels: 100, letter: "_t" },
                                { size: "small", pixels: 240, letter: "_m" },
                                { size: "medium", pixels: 500, letter: "" },
                                { size: "medium 640", pixels: 640, letter: "_z" },
                                { size: "large", pixels: 1024, letter: "_b"}];
        var loadedFlickrImages = [];
        var startingIndex;
        var numberOfFlickrItems = data.items.length;
        var loadedFlickrImagesCounter = 0;
    
        // Determine a plausible starting value for the image height
        if (el.data("scrollableAreaHeight") <= 75) {
            startingIndex = 0;
        } else if (el.data("scrollableAreaHeight") <= 100) {
            startingIndex = 1;
        } else if (el.data("scrollableAreaHeight") <= 240) {
            startingIndex = 2;
        } else if (el.data("scrollableAreaHeight") <= 500) {
            startingIndex = 3;
        } else if (el.data("scrollableAreaHeight") <= 640) {
            startingIndex = 4;
        } else {
            startingIndex = 5;
        }
    
        // Put all items from the feed in an array.
        $.each(data.items, function(index, item) {
            loadFlickrImage(item, startingIndex);
        });
    
        function loadFlickrImage(item, sizeIndex) {
            var path = item.media.m;
            var imgSrc = path.replace("_m", flickrImageSizes[sizeIndex].letter);
            var tempImg = $("<img />").attr("src", imgSrc);
    
            tempImg.load(function() {
                // Is it still smaller? Load next size
                if (this.height < el.data("scrollableAreaHeight")) {
                    // Load a bigger image, if possible
                    if ((sizeIndex + 1) < flickrImageSizes.length) {
                        loadFlickrImage(item, sizeIndex + 1);
                    } else {
                        addImageToLoadedImages(this);
                    }
                } else {
                    addImageToLoadedImages(this);
                }
    
                // Finishing stuff to do when all images have been loaded
                if (loadedFlickrImagesCounter == numberOfFlickrItems) {
                    // DO YOUR FINISHING STUFF HERE, LIKE ADDING THE
                    // LOADED IMAGES TO YOUR PAGE...
                }
    
            });
        }
    
    
        function addImageToLoadedImages(imageObj) {
    
             // YOU CAN DO OTHER STUFF HERE, LIKE CALCULATING
             // AND ADDING ADDITIONAL ATTRIBUTES TO THE IMAGE
             // ELEMENT
    
             // Add the image to the array of loaded images
             loadedFlickrImages.push(imageObj);
    
             // Increment counter for loaded images
             loadedFlickrImagesCounter++;
    
    
        }
    
    });
    

    I can't guarantee that this code is "copy/paste ready to run" since I've extracted it from a bigger context, but I hope you get the general idea.