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.
Related
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.
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.
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.
I am trying to load a 'loading' message to the user before a time-intensive function is called in javascript.
HTML:
<p id='foo'>Foo</p>
Javascript:
var foo = document.getElementById('foo');
function tellViewerLoading() {
// Tell the user that loading is occuring.
foo.innerHTML = 'loading...';
}
function someActionThatTakesALongTime() {
// Do some action that takes a long time.
var i = 0;
while(i < 100000) {i++; console.log(i);};
}
function domUpdateDelayExperiment() {
tellViewerLoading();
someActionThatTakesALongTime();
}
domUpdateDelayExperiment();
JSFiddle: http://jsfiddle.net/johnhoffman/xDRVF/
What I want to happen is for the DOM to be updated immediately after tellViewerLoading() is called.
Instead, what happens is that the DOM seems to be updated after someActionThatTakesALongTime() finishes running. At that point, it is useless to display a loading message.
How do I tell javascript to immediately update the DOM after tellViewerLoading() is called?
Spawn the long-time running function with setTimeout:
function domUpdateDelayExperiment() {
tellViewerLoading();
setTimeout(someActionThatTakesALongTime, 50);
}
Explanation: the tellViewerLoading() function updates the DOM but the browser won't reflect changes on screen until domUpdateDelayExperiment() returns. By delaying someActionThatTakesALongTime by means of setTimeout() we let the browser to reflect DOM changes. The timeout is arbitrary, but its minimum value may be important in some browsers. A value of 50 ms is fair enough.
Actually, if you step through your code using a debugger, you will see that the loading text is changed before the next function is called.
Your browser is just hanging at the long function call, so it can't change the displayed text.
Using a short timeout can help if you want your browser to have enough time to change the display before going to the next function.
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.