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.
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.
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.
How do you get the width and height in pixels image which its src is in data:image/jpeg;base64 ?
Didn't succeed using setting img.src to it and then calling width().
var img = jQuery("<img src="+oFREvent.target.result+">");
img.width() // = 0
This will work :
var img = new Image();
img.onload = function() {
alert(this.width);
}
img.src = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAA.......";
FIDDLE
I made sure the base64 was valid by converting an image here, and the onload function should come before setting the source of the image.
You have to wait for the image to be loaded (or complete, if cached). This will result in an asynchronous callback operation:
// pure JS:
var img = new Image();
img.src = myDataString;
if (img.complete) { // was cached
alert('img-width: '+img.width);
}
else { // wait for decoding
img.onload = function() {
alert('img-width: '+img.width);
}
}
Note: I once had the same problem with a project using jQuery. jQuery doesn't provide an access to the image directly enough just after you have created it. It seems, it's not possible to be done, but in pure JS. But you could try a timeout-loop and wait for the img-width to have a value (and catch any loading/decoding errors).
[Edit, see also comments] Why this works (why there is no race-condition):
JS is single-threaded, meaning there's only one part of code executed at a given time. Any events will be queued until the current scope is exited and will only fire then. Thanks to late-binding, any listeners will be evaluated only then (after the current scope has been executed). So the handler will be present and listening as soon as the onload-Event is fired, regardless, if the listener was setup before or after setting the src-attribute. In contrast to this, the complete-flag is set as soon as the src-attribute is set.
I was told it is necessary to set the onload function before setting src for an image object. I've searched in SO for this.
I found this code:
var img = new Image();
img.src = 'image.jpg';
img.onload = function () {
document.body.appendChild(img);
};
But most people believe that onload should be written before src like this:
var img = new Image();
img.onload = function () {
document.body.appendChild(img);
};
img.src = 'image.jpg';
MUST it be written in this order? Are there any cases when the above code will cause an error (like if an image is too big)?
If you anyone can show me some examples, I will be very appreciate.
It doesn't have to, but if setting the src and the image loads before your handler is attached, it won't fire.
JavaScript operates asynchronously. Setting the src will cause the web browser to load the image outside the main execution flow. If onload isn't set at the time that operation completes - which could be between setting src and onload.
As soon as you assign the src a value, the image will load. If it loads before the onload is reached, your onload will not fire.
To support ALL implementations, I strongly suggest to assign the onload handler before setting the src.
It is my experience (21+ years of JS) that you MUST set onload first - especially in IE which did not even support the image object when I started with JS.
If you get issues about the cached image not firing, add +"?"+new Date().getTime() when you set the src next time to avoid cache.
Here is the example from MDN which also uses the order I have suggested
Creating an image from scratch
Another SO link image.onload not firing twice in IE7
The browser will start downloading the image asychronously as soon as you assign a src, so there is the possibility the download could complete before you attach the onload event handler and never fire the code to add the image to the DOM.
Most browser fire the load event imediatly if the image if the image is cached.
However, Internet explorer 7 won't fire it at all. That's why it's better to set the src first.
Answers above always mentioned the same problem like:
there is the possibility the download could complete before you attach the onload event handler
or
but if setting the src and the image loads before your handler is attached, it won't fire.
or bug with IE7.
First, let's ignore IE7.
Second, I don't think problem mentioned exist, for example:
function loadImg(url) {
let img = new Image()
img.src = url
let date1 = Date.now()
console.log(img.complete)
while (Date.now() - date1 < 5000) {
}
img.onload = function() {
console.log('success')
}
console.log('sync first')
}
loadImg('https://cdn.sstatic.net/Sites/stackoverflow/img/sprites.svg')
Normally, you will get:
false
sync first
success
Well, if you executed on another site which will use cache more than one time. You will get the result like image below:
The answer is clear now.
Well, as long as you set the onload synchronously. You will not miss the onload event.
Why would I say synchronously. For another example,
function loadImg(url) {
let img = new Image()
img.src = url
let date1 = Date.now()
console.log(img.complete)
while (Date.now() - date1 < 5000) {
}
setTimeout(function() {
img.onload = function() {
console.log('success')
}
})
console.log('sync first')
}
loadImg('https://cdn.sstatic.net/Sites/stackoverflow/img/sprites.svg')
The result on another site:
The second time with cache, load event won't be triggered. And the reason is setTimeout is asynchronously.
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.