23

I want to create an object that has an image property, but I want the contstructor to finish running only once the image is loaded. Or to describe this with code:

GraphicObject = Class.extend({

    //This is the constructor
    init: function(){
          this.graphic = new Image();
          this.graphic.src = 'path/to/file.png';

          while(true)
          {
              this.graphic.onload = function(){break;};
              //I know this won't work since the 'break' is on a different context
              //but you got what I try to do.
          }
     }


})

For those who are unfamiliar with the Class notation I'm using in my script, it's based on this

Any ideas?

Loupax
  • 4,728
  • 6
  • 41
  • 68
  • 2
    So you actually want the image to block all JavaScript *(and all user interaction)* until it is loaded? –  Feb 23 '12 at 21:02
  • Yes. I want to know that if the object is created, the graphic inside it is loaded. I tried with a 'setting a flag in the callback of every instance of the object' but after doing it for every single class I'm extending it started getting old and I started looking for a more automatic way to do it... – Loupax Feb 23 '12 at 21:06
  • 2
    Would it be enough to just ask the image if it is loaded? An image has a `.complete` property that is set to `true` when loaded, so if there's some code that needs to work with the image, but only when loaded, it could check that property, and then try again every `n` milliseconds until loaded. –  Feb 23 '12 at 21:09
  • In this specific use case, I want to create a bunch of GraphicObjects, and not start any interaction unless they are all loaded. If even one cannot load, the application shouldn't run at all. No GraphicObject will be created after the initialization of the app. I'm aware that I can check if the file is loaded, but I'd rather know that "if it runs, it's loaded" because I'm lazy and got tired of writing callbacks all the time :P – Loupax Feb 23 '12 at 21:18
  • Aaaand the brain just started ticking... if(this.graphic.complere)break; I need some sleep :P – Loupax Feb 23 '12 at 21:21
  • Well, I didn't actually mean that you should use it to block. I just meant that perhaps you could structure the API for your class so that it checks the `.graphic` to see if it's complete before it does something, then perhaps retries ever `n` milliseconds until the load is complete. Anyway, if you do want to use it to block, you could just do `while(!this.graphic.complete);`. The `;` becomes the body of the `while` loop. I don't recommend this though. –  Feb 23 '12 at 21:44
  • 1
    Hm, got your point... There COULD be a lockup of the browser if for some reason the image doesn't come... I guess I'll create a preloader object... My question is considered answered though! – Loupax Feb 23 '12 at 22:17
  • Oh my god, I was SO clueless back then... – Loupax Apr 05 '16 at 07:10

5 Answers5

24

It is possible, but only with the ancient art of Base64 and Data-URL.

GIF image converted to Base64.

rune.b64

R0lGODlhIwAjAIAAAP///wAAACwAAAAAIwAjAAACf4SPqcsb3R40ocpJK7YaA35FnPdZGxg647kyqId2SQzHqdlCdgdmqcvbHXKi4AthYiGPvp9KVuoNocWLMOpUtHaS5CS54mZntiWNRWymn14tU7c2t6ukOJlKR5OiNTzQ7wb41LdnJ1coeNg3pojGqFZniPU4lTi0d4mpucmpUAAAOw==

JavaScript which loads the converted image form the same server via blocking AJAX.

loader.js

var request = new XMLHttpRequest();
var image = document.createElement('img');

request.open('GET', 'rune.b64', false);
request.send(null);

if (request.status === 200) {
  image.src= 'data:image/gif;base64,' + request.responseText.trim();

  document.getElementsByTagName("body")[0].appendChild(image); 
}

Problems

  • Some older browsers don't like (big) Data-URLs
  • Base64 encoding makes images about 37% bigger
  • The whole UI is blocked till the image is loaded

This is a very-evil way

K..
  • 4,044
  • 6
  • 40
  • 85
  • 6
    +1 because, hey, it *does* answer the question; and you warned that it's evil (which it is). – rvighne Jan 25 '14 at 06:21
  • 16
    For the record, this still loads the actual image src asynchronously. – Naatan Jul 24 '14 at 19:37
  • Really? When I put a `console.log(image.complete);` and `console.log(image.naturalWidth)` directly behind the `image.src =` the output suggests the image is loaded. – K.. Jul 24 '14 at 21:27
  • @Naatan, K.. Do you have evidence / data for your claims? Could the behavior depend on browsers / image sizes? Are there any limitation? Is this specified somewhere? – Stefan Haustein Jun 16 '16 at 12:37
  • 1
    XMLHttpRequest's *allways* do their job asynchronously. – Noctisdark Oct 21 '16 at 06:38
  • @Naatan, while it may be factually true, it's functionally irrelevant, isn't it? No new asynchronous connection to the server is made, all the data for the image is in place, and whether or not the image is in cache is irrelevant. Given today's tech, this is an inefficient solution (not really evil, just avoid it unless you must). If anything, the real problem is the possibility of this ancient brute-force method being deprecated in future browsers. (Maybe this was a bigger issue back in 2013.) – JBH Jul 24 '18 at 20:49
  • 1
    This answer is wrong (at least in recent versions of Chrome). I tried `image.src = '...data url...'; console.log(image.complete, image.naturalWidth, image.naturalHeight)` in console and the result was `false 0 0`. – trusktr Feb 25 '19 at 20:56
21

There is a non-evil way to load images in Javascript synchronously.

loadImage = async img => {
    return new Promise((resolve, reject) => {
        img.onload = async () => {
            console.log("Image Loaded");
            resolve(true);
        };
    });
};

Call it with await anywhere. like this

for(let i=0;i<photos.length;i++){
    await loadImage(photos[i]);
}

It will load all images one by one.

Note: Calling function must be async to use await

shivampip
  • 2,004
  • 19
  • 19
12

Put the dependent code in the callback. There is no other non-evil way.

GraphicObject = Class.extend({

    //This is the constructor
    init: function(){
          this.graphic = new Image();
          this.graphic.onload = function ()
          {
              // the rest of the ctor code here
          };
          this.graphic.src = 'path/to/file.png';
     }
});
Matt Ball
  • 354,903
  • 100
  • 647
  • 710
  • 1
    What do you mean when you say 'non-evil'? – Loupax Feb 23 '12 at 21:07
  • 1
    Javascript is single threaded, you can't do what you want without locking up the user browser. If there's any code that needs to access the object properties after its constructed, make the constructor accept an onload parameter and call it after the image is loaded. –  Feb 23 '12 at 21:43
  • 8
    The "non-evil" is an overstatement. For somebody, wrapping functions in thousands of different callbacks may be not the best solution. – luke1985 Mar 25 '14 at 13:33
  • Its funny how he says "There is no other non-evil way" yet theres one thats 2 spots above his answer. Use promises... This is 2020 – Mister SirCode Jul 22 '20 at 01:42
  • 1
    @MisterSirCode javascript promises and `await` did not exist when I wrote this answer eight-plus years ago. Times change :) – Matt Ball Jul 22 '20 at 14:13
7
var timeOut = 5*1000; //ms - waiting for max 5s to laoad
var start = new Date().getTime();
while(1)
  if(img.complete || img.naturalWidth || new Date().getTime()-start>timeOut)
    break;

Based on this answer.

Community
  • 1
  • 1
  • 1
    This solves the issue and restores proper functionality, however with the advent of the promise let it be noted we can now wrap img.onload in a Promise and await() it! The joys of technological advances. – tom Mar 05 '18 at 19:16
  • 1
    this hilariously evil but i think it's the only answer that is actually synchronous – Rico Kahler Sep 19 '18 at 21:36
-6

I wrapped it into function, and it worked!

for (var i = 0, i < asset.length; i++) {

    var img = new Image();
    img.src = "file:///" + folder + "/" + asset[i].name;

    getWdrp(img);

    function getWdrp (img) {
        img.onload = function(){
            // work with the image file
        }
    }
}

This is an example that worked for me, because before, when I was processing the image without wrapping in the function, it would work async, now it is async.

AmazingDayToday
  • 3,724
  • 14
  • 35
  • 67