Javascript image.onload() guarantees image is loaded? - javascript

I am working on an HTML5 game which uses a lot of images.
However, sometimes the game does not load properly and throws some error messages that suggest that some images have not been loaded correctly.
I use a preloading function, incrementing a variable before starting to set the src of an image and decrementing it in the onload() function. Only when this variable reached 0, i start drawing. Still sometimes I (and other users) see errors and the game doesnt load. Most of the time it works though.
Now I wonder... technically this should not be possible. Does the call of the onload function guaratee the image is loaded ? Because I feel it doesnt.
Here the code although I dont think it matters:
var ressourcesToLoad = 1;
// all the loadImage() calls
// ...
ressourceLoaded();
function ressourceLoaded()
{
ressourcesToLoad--;
// if(ressourcesToLoad == 0) start main loop
}
function loadImage(imgFile)
{
ressourcesToLoad++;
var img = new Image();
img.onload = ressourceLoaded();
img.src = imgFile;
return img;
}

Yes, BUT as far as I remember:
1 In some browsers it can fire twice in a row for the same image
2 In some browsers it doesn't fire when image is loaded from cache
3 I'm not sure if it fires when server returns 404 for the image
And probably you should start loading next image only after loading previous. If you have a lot of big images on the same domain and start them loading simultaneously, the "two connections per page" rule can break something for you.
P.S.: By some "some browsers" I mean "some browsers or their outdated versions".

No the complete property tells you more accurate if the image has finished loading. Unfortunately there is no event that will be triggered when loading is done. One way could be to poll this property until it is set to true.

Related

Does the order of either onload or addEventListener and setting an image's src actually matter?

I've held it as an "absolute truth" for a long time that doing:
let img = new Image();
img.src = '... some url ...'
img.onload = function() { console.log('onload') }
was wrong, because the .onload handler might not be called. Same thing for attaching a handler using .addEventListener('load', function() ... ) after setting .src: it might not work. (see this example answer stating this: "... Add event listener before assigning a value to the src attribute ...")
Context for this question: I've found an example of setting .src before calling .addEventListener in the popular p5.js library, specifically here. I would like to fix this and submit a PR. However, I would also like to create a unit test that fails with the current code (setting .src = before calling .addEventListener) and passes with my fix.
However! I haven't been able to actually see under which circumstances setting .src before actually fails...!
Here is some code to demonstrate what I mean:
(function f() {
let i = new Image();
i.src = '//placekitten.com/10/10';
document.querySelector('body').appendChild(i);
let out = 0;
for(let i = 0; i < 1000000000; i++) { out *= i * 100 - out * 2; } // just to delay
i.onload = function() { console.log('onload') }
i.addEventListener('load', function() { console.log('load event') });
})();
The 1000000000-iteration loop takes a few seconds to complete, as I'm trying to get the Image element to be added to the DOM and the image URL to be loaded early enough that the .onload and .addEventListener handlers would fail... But they work!
I tried with a very short data URL instead of placekitten.com, same result. I tried loading the placekitten.com and the data URL images before running my test code (to make sure those images are cached), but that doesn't work either.
The only way I can get the .onload and .addEventListener handlers to not be called is to wrap them in a setTimeout...
Is JavaScript's single-threadneness invalidating the "truth" that .src should always be set after setting event listeners? Or is this a change that happened in modern browsers, and it used to be true in older browsers that setting .src before attaching listeners would sometimes fail? Thanks!
The order doesn't matter. The load event is always fired asynchronously (queued on the event loop by the loading algorithm), after the synchronous execution has completed, and will evaluate which event listeners are installed and need to be executed only then.
Given the answer that you linked and especially image.onload event and browser cache / jQuery callback on image load (even when the image is cached), it appears older browsers might have fired the event synchronously (or not at all) if the image was loaded from the cache.

JavaScript: thread safety with asynchronous event callbacks? (do I need 'volatile' or something?)

Suppose I have the following code to preload 2 images, and do something only after both images have loaded:
var numLoaded = 0;
function OnImageLoaded()
{
numLoaded++;
if (numLoaded==2) alert("Got 'em!");
}
var a = new Image();
var b = new Image();
a.onload = OnImageLoaded;
b.onload = OnImageLoaded;
a.src = 'foo.jpg';
b.src = 'bar.jpg';
Assigning a.src and b.src causes both images to be loaded in the background, and OnImageLoaded() being called then they're ready.
Obviously the code after if (numLoaded==2) is supposed to run only once.
Now, is there a possibility of this happening:
the first image is loaded, the function gets called, numLoaded++ increases the counter to one,
the second image is loaded, the function gets called (in a diffrent thread), numLoaded++ increases the counter to two,
the first thread checks numLoaded==2 which is now true, so the code gets executed,
the second thread also checks numLoaded==2 which is still true, so the supposed-to-run-once code gets executed again ← problem!
Is this a chance (albeit very small) and how do I avoid this? In other languages I'd use a Critical Section for this, or a mutex, or make numLoaded volatile and do n=(++numLoaded) or something, but how do I go about this in JS?
Javascript does not run concurrently under normal circumstances. There are ways of doing so using Node.js but it's rather difficult and not something you have to worry about for typical situations.

Javascript : Loading images via Javascript

This is a chunk of javascript code from a tutorial where they are trying to load an image onto a canvas and do some manipulations later on. I have omitted most of the irrelevant code to make it simpler to understand.
1) I fail to understand why the line containing the filename of the image is always put below the imageObj.onload function . Does it matter ? At what point does the image start getting loaded ?
2) What will happen if I forget to put the source of the image file.
<script>
window.onload = function(){
....
var imageObj = new Image();
imageObj.onload = function(){
....
....
});
....
....
};
imageObj.src = "yoda.jpg";
};
</script>
This is a somewhat historical issue. The order from .onload and .src doesn't really matter (it'll work technically on both orders), the issue is that some browsers (some = Internet Explorers) will take the image from the cache if available, as soon as the src attribute is set.
That is why you should always declare an onload handler before setting src.
If you just forget to set the src attribute, just nothing will happen at all. If you don't hold any more references or closures to that object, it will just get garbage collected as soon as possible.
Loading is triggered by setting the .src property.
On (some?) older browsers, the handler is not called if it's registered after the property is set, especially if the image was already in cache and therefore "loaded" immediately.
If you forget to set the attribute, nothing will ever happen.
window.onload = function(){
// This is function 1
// This portion will execute when window has loaded completely.
// In simple words, page has been downloaded completely.
var imageObj = new Image();
imageObj.onload = function(){
// This is function 2
// This portion will execute when image has loaded completely
});
....
....
};
imageObj.src = "yoda.jpg";
So, function 1 and function 2 will execute after this line imageObj.src = "yoda.jpg";
This is answer to your first question. Putting it below does not means, it will execute after function 2. In javascript, code executes sequentially from top to bottom, but code inside functions will only execute when that function is called.
If you wont give src attribute, there would be no image to download, and thus function 2 wont get called.
Loading starting when you set src attribute. And img.onload function will be called after successful loading of the image.
changing the src triggers the "loading sequence", and due to the nature of JS to sequentially execute, you must register the handler before you load the image.
not changing the src will not trigger the loading sequence.

image.Onload does not complete the image

I need to load an array of images in Javascript, but I want to make sure that all the images are loaded before starting drawing them. So, I busy-wait for every image onLoad event to be called. First I create images and set their source and onload function:
// Load images from names
for (i = 0; i < this.nImages; i++) {
this.imagesArray[i] = new Image();
this.imagesArray[i].onload = this.onLoad(i);
this.imagesArray[i].src = images[i];
}
This is the onLoad function, member of the class I'm developing (the first two steps were in the constructor):
MyClass.prototype.onLoad = function (nimage) {
console.log ("Image completed? ", nimage, " ", this.imagesArray[nimage].complete);
this.imagesLoaded++;
}
Then I busy wait for all the onLoad functions to increment the counter (again, in the constructor):
while (this.imagesLoaded < this.nImages) {
// This is busy wait, and I don't like it.
continue;
}
So far, so good. But when I try to draw it on an HTMl5 canvas with my drawClass:
MyClass.prototype.refresh = function () {
// Gets one of the images in the range
var imageNum = this.GetImageNum();
// Test for completeness. This gives FALSE :(
console.log ("completeness for image number ", imageNum, " is: ", this.imagesArray[imageNum].complete);
this.drawClass.draw(this.imagesArray[imageNum], this.xOrigin, this.yOrigin);
}
The console.log line gives false and I get the infamous NS_ERROR_NOT_AVAILABLE exception.
Please not that the refresh() function is called after the onLoad() function, according to Firebug.
What am I missing here?
You need to assign onload before setting the source, otherwise the loading may be completed before the script gets to set the handler. Maybe that already fixes it.
Re the busy waiting, that is indeed never a good thing. It's hard to suggest alternatives, as you are not showing why you need to wait in the first place. But what might be a good idea is extending the onload handler to detect whether the image array is complete, and if it is, to start the following action - that would make the busy waiting unnecessary.

Javascript block script execution

I need to do something like this:
Execute a piece of code
Start to load an image and block the script execution
When the image is loaded resume the execution
Execute the rest of the code
I know that the simplest way is to assign a function on the onload event of the image and then execute the rest of the code in the function, but if it's possible i want to have a "linear" behaviour blocking the script execution and then resume it.
So, is there a cross-browser way to do this?
The only way to block script execution is to use a loop, which will also lock up most browsers and prevent any interaction with your web page.
Firefox, Chrome, Safari, Opera and IE all support the complete property, which was finally standardised in HTML5. This means you can use a while loop to halt script execution until the image has finished downloading.
var img = new Image();
img.src = "/myImage.jpg";
document.body.appendChild(img);
while (!img.complete)
{
// do nothing...
}
// script continues after image load
That being said, I think you should look at ways of achieving your goal without locking up the browser.
If you don't mind having a preprocessing step, try Narrative Javascript, which you can
image.onload = new EventNotifier();
image.onload.wait->();
This suggestion is not exactly what you asked for, but I offer it as a possible alternative.
Create a CSS class with the background-image you want to use. When your app starts, assign this CSS class to a DIV that is either hidden out of site or sized to zero by zero pixels. This will ensure the image is loaded from the server. When you want to load the image (step two above), use the CSS class you create; this will happen quickly. Maybe quickly enough that you need not block the subsequent code execution?
I wouldn't try to block script execution completely, as that could make the browser slow down, or even alert the user that a script is taking too long to execute.
What you can do is 'linearize' your code by using events to finish work. You will need to add a time out to the function, as the image may never load.
Example:
var _img = null;
var _imgDelay = 0;
var _finished = false;
function startWork(){
_img = document.createElement('img');
_img.onload = onImgLoaded;
_img.src = 'yourimg.png';
// append img tag to parent element here
// this is a time out function in case the img never loads,
// or the onload event never fires (which can happen in some browsers)
imgTimeout();
}
function imgTimeout(){
if (_img.complete){
// img is really done loading
finishWork();
}
else{
// calls recursively waiting for the img to load
// increasing the wait time with each call, up to 12s
_imgDelay += 3000;
if (_imgDelay <= 12000){ // waits up to 30 seconds
setTimeout(imgTimeout, _imgDelay);
}
else{
// img never loaded, recover here.
}
}
}
function onImgLoaded(){
finishWork();
}
function finishWork(){
if (!_finished){
// continue here
_finished = true;
}
}
You can use xmlhttprequest and use synchronous mode.
var url = "image.php?sleep=3";
var img = new Image;
var sjax = new XMLHttpRequest();
img.src = url;
sjax.open("GET", url, false);
sjax.send(null);
alert(img.complete);
The trick here is we load the same image twice, first by using the Image object, and also by using ajax in synchronous mode. The Image object isn't needed, I just assumed that's how you want to load it. The key though is that if ajax completes, then the image will be fully downloaded an in the browser's cache. As such, the image will also be available for use by the Image object.
This does assume that the image is served with cache friendly http headers. Otherwise, it's behavior might vary in different browsers.

Categories