3

I have a problem with jQuery.

I wanted to get the real width/height of an img.
Now I used this function to get it:

var imageSize = function(image) {
    var realImageSize = { };

    $("<img/>") // Make in memory copy of image to avoid css issues
    .attr("src", $(image).attr("src"))
    .load(function() {
        realImageSize.width = this.width;   // Note: $(this).width() will not
        realImageSize.height = this.height; // work for in memory images.

        alert(realImageSize['height']); // Valid height
    });

    alert(realImageSize['height']); // Undefined

    return realImageSize;
}

So I'm copying the image in memory, and then I get and set the images real size in the .load method.

Now, the problem is that the var realImageSize variable isn't available in this scope for some reason. Can anyone tell me why?

IluTov
  • 6,807
  • 6
  • 41
  • 103
  • `load()` is asynchronous, and as such will have exited before your callback is called. What you are trying to do is not possible. You need to handle all logic dependant on the `load()` within the callback. – Rory McCrossan Jan 31 '13 at 10:02
  • 1
    It's similar (maybe a duplicate, maybe not) to this question : http://stackoverflow.com/questions/12475269/variable-doesnt-get-returned-from-ajax-function – Denys Séguret Jan 31 '13 at 10:04

4 Answers4

6

That's not a problem of scope, it's fine, but a problem of synchronicity : you're reading the value before the load callback has been executed.

The callback you give as argument to load isn't executed immediately but only after the image has been loaded.

The usual solution is to not return a value but provide a callback :

var fetchImageSize = function(image, callback) {
    $("<img/>") // Make in memory copy of image to avoid css issues
    .load(function() {
        var realImageSize = { };
        realImageSize.width = this.width;   // Note: $(this).width() will not
        realImageSize.height = this.height; // work for in memory images.
        callback(realImageSize);
    }).attr("src", image.src);
};

fetchImageSize(myImage, function(realImageSize){
    alert(realImageSize['height']); // NOT Undefined
});

Note that you should always set the src after you set the onload callback. Some browsers, if you have the image in cache, might not call the onload callback if you set it after you set the source.

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
2

your second alert is called before the load function is completed.use callbacks...

try this

var imageSize = function(image,callback) {
  var realImageSize = { };

  $("<img/>") // Make in memory copy of image to avoid css issues
  .attr("src", $(image).attr("src"))
  .load(function() {
    realImageSize.width = this.width;   // Note: $(this).width() will not
    realImageSize.height = this.height; // work for in memory images.

    alert(realImageSize['height']); // Valid height
    callback(realImageSize);
  });


}

imageSize(img, function(realImageSize) {
   alert(realImageSize.height);
});
bipen
  • 36,319
  • 9
  • 49
  • 62
  • Thanks! Unfortunately this won't work, because a style `width` could be applied to the img. – IluTov Jan 31 '13 at 10:04
2

Use a callback. The other answers are explaining the asynchronous stuff, I'm not repeating :-)

var imageSize = function(image, callback) {
.
.
.
    .load(function() {
        realImageSize.width = this.width;   // Note: $(this).width() will not
        realImageSize.height = this.height; // work for in memory images.

        callback(realImageSize);
    });

Then, you can call the function like this:

imageSize(img, function(size) {
    alert(size.height);
});
Florian Margaine
  • 58,730
  • 15
  • 91
  • 116
2

This is a common problem for Javascript programmer who come from other languages. Javascript uses an execution model called Asynchronous calls. In a nutshell when you register a function for the .load() event, the execution will of the function will be postponed to when the image has been loaded while the rest of the function body (ie. second alert()) executes immediately:

var imageSize = function(image) {
    //runs now
    var realImageSize = { };

    //runs now
    $("<img/>") // Make in memory copy of image to avoid css issues
    .attr("src", $(image).attr("src"))
    .load(function() {
        //this code can run say 5 sec later
        realImageSize.width = this.width;   // Note: $(this).width() will not
        realImageSize.height = this.height; // work for in memory images.

        alert(realImageSize['height']); // Valid height
    });

    //runs now. The realImageSize variable is yet to be created in 5 sec
    alert(realImageSize['height']); // Undefined

    //runs now
    return realImageSize;
}

A common pattern to solve this kind of issue is to pass a callback to the function that is going to take long.

//** Get an image URL and a callback function that will be called when the image is loaded and the size is detected
function imageSize ( src, callback ) {
    $("<img src="' + src + '"/>").load( function () {
        //here we call the callback which will be called when the image is loaded
        callback({
            width: this.width,
            height: this.height
        });
    });
}

imageSize( 'my/image/path.png', function ( imageSize ) {
    //this callback function will be called when the image is loaded
    alert( 'width: ' + imageSize.width + ', height: ' + imageSize.height );
});
AlexStack
  • 16,766
  • 21
  • 72
  • 104