-1

I'm having a problem with a Javascript function that apparently gets called too soon. I have a hunch this is a hoisting problem, but I'm not sure.

So, I have this function which is assigned to onclick of an <img>:

function setModalPicture(picName){
    
    //Build the path to the picture
    var pic= 'assets/img/art/'+picName;
      
    //Set the picture
    $('#g-modal-img').attr('src', pic);    
    
    adjustModalPadding();          
    
}

The intention was that the src attribute of #g-modal-img should be set to the img, and only then should adjustModalPadding be called. This is because adjustModalPadding needs the height of #g-modal-img, which is zero before src is set to the <img>. However, I noticed that this doesn't work properly, and if I make adjustModalPadding log the height of #g-modal-img to the console, it shows zero. I think this means that the function is called before src is set to the <img>.

bren
  • 4,176
  • 3
  • 28
  • 43
Nicola
  • 379
  • 3
  • 14
  • 3
    The issue is the image's dimensions aren't known until the image is successfully *loaded*. You need to add an `onload` listener to the image. See http://stackoverflow.com/a/626505/1902010 on the duplicate question. – ceejayoz Nov 17 '16 at 14:51
  • 2
    Hoisting isn't going to execute your function any sooner. – Gavin Nov 17 '16 at 14:51
  • 1
    And JavaScript **is** a "regular" programming language. – T.J. Crowder Nov 17 '16 at 14:52
  • I didn't mean to sound disrespectful of Javascript. :) I thought this was a hoisting problem, and since I had never seen hoisting before Javascript, it's not a 'regular' language to me in this sense. No offense meant. :) – Nicola Nov 17 '16 at 15:33
  • Also, of course it's about the image not being loaded yet! Sorry, didn't think it through. I actually saw something similar in a tutorial, but can't remember for the life of me how it was dealt with there. – Nicola Nov 17 '16 at 15:40

1 Answers1

3

You need to wait for the image to load:

function setModalPicture(picName){

    //Build the path to the picture
    var pic= 'assets/img/art/'+picName;

    //Set the picture
    var img = $('#g-modal-img');
    img.one("load", adjustModalPadding).attr('src', pic);
    if (img[0].complete) {
        img.off("load", adjustModalPadding);
        adjustModalPadding();          
    }
}

Note the sequence above, because it's important:

  1. First, hook the load event with a one-off handler (one).
  2. Then, set the src.
  3. Check if the image is already complete: If so, remove the handler and call your function immediately; otherwise, when load fires, it will call adjustModalPadding and remove it as a handler.

You may want to add error handling to that...


Here's a working example:

function setModalPicture(picName) {

  //Build the path to the picture
  var pic = picName; // 'assets/img/art/'+picName;

  //Set the picture
  var img = $('#g-modal-img');
  img.one("load", adjustModalPadding).attr('src', pic);
  console.log("img[0].complete after setting src: " + img[0].complete);
  if (img[0].complete) {
    img.off("load", adjustModalPadding);
    adjustModalPadding();
  }
}

function adjustModalPadding() {
  var img = $("#g-modal-img")[0];
  console.log("Size: " + img.width + "x" + img.height);
}
$("input[type=button]").on("click", function() {
  console.log("img[0].complete before starting: " + $("#g-modal-img")[0].complete);
  setModalPicture("https://graph.facebook.com/1035045703246692/picture?type=large");
});
<!-- In a comment, you said it starts out with src="" -->
<img id="g-modal-img" src="">
<input type="button" value="Click Me">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

That works for me in Chrome, Firefox, and IE11.


Alternately, you might create a replacement img element by cloning:

function setModalPicture(picName) {

  //Build the path to the picture
  var pic = picName; // 'assets/img/art/'+picName;

  //Set the picture
  var img = $("#g-modal-img");
  var newImage = img.clone();
  img.replaceWith(newImage);
  newImage.one("load", adjustModalPadding).attr('src', pic);
  console.log("newImage[0].complete after setting src: " + newImage[0].complete);
  if (newImage[0].complete) {
    newImage.off("load", adjustModalPadding);
    adjustModalPadding();
  }
}

function adjustModalPadding() {
  var img = $("#g-modal-img")[0];
  console.log("Size: " + img.width + "x" + img.height);
}
$("input[type=button]").on("click", function() {
  console.log("img[0].complete before starting: " + $("#g-modal-img")[0].complete);
  setModalPicture("https://graph.facebook.com/1035045703246692/picture?type=large");
});
<!-- In a comment, you said it starts out with src="" -->
<img id="g-modal-img" src="">
<input type="button" value="Click Me">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

That also works for me in Chrome, Firefox, and IE11.


Finally, you might create a replacement img element from scratch (not cloning):

function setModalPicture(picName) {

  //Build the path to the picture
  var pic = picName; // 'assets/img/art/'+picName;

  //Set the picture
  var img = $("#g-modal-img");
  var newImage = $("<img>").attr("id", "g-modal-img");
  img.replaceWith(newImage);
  newImage.one("load", adjustModalPadding).attr('src', pic);
  console.log("newImage[0].complete after setting src: " + newImage[0].complete);
  if (newImage[0].complete) {
    newImage.off("load", adjustModalPadding);
    adjustModalPadding();
  }
}

function adjustModalPadding() {
  var img = $("#g-modal-img")[0];
  console.log("Size: " + img.width + "x" + img.height);
}
$("input[type=button]").on("click", function() {
  console.log("img[0].complete before starting: " + $("#g-modal-img")[0].complete);
  setModalPicture("https://graph.facebook.com/1035045703246692/picture?type=large");
});
<!-- In a comment, you said it starts out with src="" -->
<img id="g-modal-img" src="">
<input type="button" value="Click Me">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • @richdotjs: No, `one`, not `on`. It's a one-off handler. – T.J. Crowder Nov 17 '16 at 14:54
  • Hmm. I tried your approach—as in, I copypasted your code as it is—but it behaves exactly in the same way. Height of the picture is still zero when adjustModalPadding is executed. Is complete an actual method/property? I'd say not (missing () and it doesn't show in the autocomplete menu). Maybe you meant it as a shorthand for 'check if the image is complete', but I don't know how to do that, actually. – Nicola Nov 17 '16 at 15:39
  • @Nicola: `complete` is an actual property: https://www.w3.org/TR/html5/embedded-content-0.html#dom-img-complete I'd be fairly surprised if the above didn't work. But if it doesn't, you might consider *replacing* `#g-modal-img` with a whole new `img` element rather than just changing its `src` (again hooking `load` before setting `src`, etc.). – T.J. Crowder Nov 17 '16 at 15:43
  • I'll try that, thanks – Nicola Nov 17 '16 at 15:44
  • @Nicola: I've added a working example. – T.J. Crowder Nov 17 '16 at 15:53
  • I think I know what the problem is with the first approach you suggested. I tried alert(img[0] .complete) before assigning pic to src, and it says true. This is strange, because the existing img element has src="", which I think should be none of the conditions in the link you provided. – Nicola Nov 17 '16 at 15:55
  • @Nicola: That's not surprising, but setting `src` should change `complete` back to `false` (unless the browser can satisfy the image from cache and decides to do that synchronously). See the updated example starting with `src=""`. – T.J. Crowder Nov 17 '16 at 15:59
  • For some reason, the snippet doesn't run. Not my lucky day... – Nicola Nov 17 '16 at 16:02
  • Also, I hooked to the load event of the img a function returning the height of the picture. Result: right after the picture loads, it says its height is zero. Too bad the picture is clearly visible and definitely taller than zero... – Nicola Nov 17 '16 at 16:20
  • Works with this pathetic workaround: //Set the picture $('#g-modal-img').attr('src', pic); setTimeout(adjustModalPadding, 250); Anything less than 250 is likely to make it fail. The higher the interval, the likelier it'll work. Clealry I can't settle for this, but this is quite crazy. – Nicola Nov 17 '16 at 16:29
  • @Nicola: Ugh, sorry to hear that. The above works for me in Chrome, Firefox, and IE11. What are you using? – T.J. Crowder Nov 18 '16 at 08:21
  • 1
    I'm using Firefox, but I solved the issue by finding a CSS way to do what adjustModalPadding was doing. No more need to check if the image has loaded or not :) Thanks for your help! – Nicola Nov 18 '16 at 14:27
  • @Nicola: Almost always best to do it with CSS if you can! :-) Congrats on that. – T.J. Crowder Nov 18 '16 at 14:28