Javascript - execute after all images have loaded - javascript

Having read other people's questions I thought
window.onload=...
would answer my question. I have tried this but it executes the code the instant the page loads (not after the images load).
If it makes any difference the images are coming from a CDN and are not relative.
Anyone know a solution? (I'm not using jQuery)

Want a one-liner?
Promise.all(Array.from(document.images).filter(img => !img.complete).map(img => new Promise(resolve => { img.onload = img.onerror = resolve; }))).then(() => {
console.log('images finished loading');
});
Pretty backwards-compatible, works even in Firefox 52 and Chrome 49 (Windows XP era). Not in IE11, though.
Replace document.images with e.g. document.querySelectorAll(...) if you want to narrow the image list.
It uses onload and onerror for brevity. This might conflict with other code on the page if these handlers of the img elements are also set elsewhere (unlikely, but anyway). If you're not sure that your page doesn't use them and want to be safe, replace the part img.onload = img.onerror = resolve; with a lengthier one: img.addEventListener('load', resolve); img.addEventListener('error', resolve);.
It also doesn't test whether all images have loaded successfully (that there are no broken images). If you need this, here's some more advanced code:
Promise.all(Array.from(document.images).map(img => {
if (img.complete)
return Promise.resolve(img.naturalHeight !== 0);
return new Promise(resolve => {
img.addEventListener('load', () => resolve(true));
img.addEventListener('error', () => resolve(false));
});
})).then(results => {
if (results.every(res => res))
console.log('all images loaded successfully');
else
console.log('some images failed to load, all finished loading');
});
It waits until all images are either loaded or failed to load.
If you want to fail early, with the first broken image:
Promise.all(Array.from(document.images).map(img => {
if (img.complete)
if (img.naturalHeight !== 0)
return Promise.resolve();
else
return Promise.reject(img);
return new Promise((resolve, reject) => {
img.addEventListener('load', resolve);
img.addEventListener('error', () => reject(img));
});
})).then(() => {
console.log('all images loaded successfully');
}, badImg => {
console.log('some image failed to load, others may still be loading');
console.log('first broken image:', badImg);
});
Two latest code blocks use naturalHeight to detect broken images among the already loaded ones. This method generally works, but has some drawbacks: it is said to not work when the image URL is set via CSS content property and when the image is an SVG that doesn't have its dimensions specified. If this is the case, you'll have to refactor your code so that you set up the event handlers before the images begin to load. This can be done by specifying onload and onerror right in the HTML or by creating the img elements in the JavaScript. Another way would be to set src as data-src in the HTML and perform img.src = img.dataset.src after attaching the handlers.

Here is a quick hack for modern browsers:
var imgs = document.images,
len = imgs.length,
counter = 0;
[].forEach.call( imgs, function( img ) {
if(img.complete)
incrementCounter();
else
img.addEventListener( 'load', incrementCounter, false );
} );
function incrementCounter() {
counter++;
if ( counter === len ) {
console.log( 'All images loaded!' );
}
}
Once all the images are loaded, your console will show "All images loaded!".
What this code does:
Load all the images in a variable from the document
Loop through these images
Add a listener for the "load" event on each of these images to run the incrementCounter function
The incrementCounter will increment the counter
If the counter has reached the length of images, that means they're all loaded
Having this code in a cross-browser way wouldn't be so hard, it's just cleaner like this.

Promise Pattern will solve this problem in a best possible manner i have reffered to when.js a open source library to solve the problem of all image loading
function loadImage (src) {
var deferred = when.defer(),
img = document.createElement('img');
img.onload = function () {
deferred.resolve(img);
};
img.onerror = function () {
deferred.reject(new Error('Image not found: ' + src));
};
img.src = src;
// Return only the promise, so that the caller cannot
// resolve, reject, or otherwise muck with the original deferred.
return deferred.promise;
}
function loadImages(srcs) {
// srcs = array of image src urls
// Array to hold deferred for each image being loaded
var deferreds = [];
// Call loadImage for each src, and push the returned deferred
// onto the deferreds array
for(var i = 0, len = srcs.length; i < len; i++) {
deferreds.push(loadImage(srcs[i]));
// NOTE: We could push only the promise, but since this array never
// leaves the loadImages function, it's ok to push the whole
// deferred. No one can gain access to them.
// However, if this array were exposed (e.g. via return value),
// it would be better to push only the promise.
}
// Return a new promise that will resolve only when all the
// promises in deferreds have resolved.
// NOTE: when.all returns only a promise, not a deferred, so
// this is safe to expose to the caller.
return when.all(deferreds);
}
loadImages(imageSrcArray).then(
function gotEm(imageArray) {
doFancyStuffWithImages(imageArray);
return imageArray.length;
},
function doh(err) {
handleError(err);
}
).then(
function shout (count) {
// This will happen after gotEm() and count is the value
// returned by gotEm()
alert('see my new ' + count + ' images?');
}
);

Using window.onload will not work because it fires once the page is loaded, however images are not included in this definition of loaded.
The general solution to this is the ImagesLoaded jQuery plugin.
If you're keen on not using jQuery at all, you could at least try converting this plugin into pure Javascript. At 93 significant lines of code and with good commenting, it shouldn't be a hard task to accomplish.

You can have the onload event on the image that can callback a function that does the processing... Regarding how to handle if all images are loaded, I am not sure if any of the following mechanisms will work:
have a function that counts the number of images for which onload is called, if this is equal to the total number of images on your page then do your necessary processing.

<title>Pre Loading...</title>
</head>
<style type="text/css" media="screen"> html, body{ margin:0;
padding:0; overflow:auto; }
#loading{ position:fixed; width:100%; height:100%; position:absolute; z-index:1; ackground:white url(loader.gif) no-repeat center; }**
</style>
<script> function loaded(){
document.getElementById("loading").style.visibility = "hidden"; }
</script>
<body onload="loaded();"> <div id="loading"></div>
<img id="img" src="avatar8.jpg" title="AVATAR" alt="Picture of Avatar
movie" />
</body>

I was looking for something like this, if you won't mind using setInterval this code is easy and straightforward. In my case I'm okay to use setInterval because it will run maybe 4-5 times.
const interval = setInterval(() => {
const allImagesLoaded = [...document.querySelectorAll('img')]
.map(x => x.complete)
.indexOf(false) === -1;
if (allImagesLoaded) {
window.print();
clearInterval(interval);
}
}, 500);

I was about to suggest the same thing Baz1nga said.
Also, another possible option that's maybe not as foolproof but easier to maintain is to pick the most important/biggest image and attach an onload event to only that one.
the advantage here is that there's less code to change if you later add more images to your page.

This works great:
$(function() {
$(window).bind("load", function() {
// code here
});
});

Related

How can I detect all images loaded inside an HTML binding?

In my components template i have a container like this
<div [innerHTML]="somehtml | safeHtml"></div>
The HTML contains images with standard image tags
like
<img src="http://google.com/someimage.png"/>
I want to know when the images inside my HTML are fully loaded. How can i check for this?
P.S.: safehtml is just a sanitizer-pipe
EDIT: I am looking for an event based solution rather than check in time-intervals! Also i need a solution specificly for this container
You can try something like this. I don't love it since it's not the angular way, but since your template is a string, I hardly believe there's something better:
HTML:
<div [innerHTML]="somehtml | safeHtml" id="div-to-check"></div>
TS:
this.somehtml = `your html`;
window.setTimeout(() => { // Let's make it async to execute it in the next tick
const promises = Array.from(
document.querySelectorAll('#div-to-check img')
) // get all images
.map((img: HTMLImageElement) => {
if (img.complete) {
return Promise.resolve(); // Check if the image already loaded. Maybe it's been too fast
}
return new Promise((resolve, reject) => {
img.onload = resolve; // it resolves when it loads
img.onerror = reject; // avoids infinite waiting
});
});
Promise.all(promises)
.then(() => {
console.log('all images loaded!');
})
.catch(() => {
console.error('an image didn\'t load');
});
});
Next time please read some other questions first, and try to solve your problem yourself, before writing your own question
But here's the solution:
jQuery(window).load(function () {
alert('page is loaded');
setTimeout(function () {
alert('page is loaded and 1 minute has passed');
}, 60000);
});

Waiting for multiple iFrames to load before executing function

Forgive my naivety, this probably is quite obvious, I just can't see it now.
Please tell me what is wrong with the following code:
$('#iframe1').load(function(){
$('#iframe2').load(function(){
alert('loaded!');
});
});
The idea is to wait until both iframes have fully loaded, then alert "loaded" - of course this is a simplified example for the sake of stack.
The script sits in script tags at the end of the body of the html doc.
#Quertiy answer is perfectly fine, but not very jQuery-ish. It is hard-coded for 2 iframes only.
The beauty of jQuery is that you can make it work for the most number of people, with as little friction as possible.
I've advised a very simplistic plugin that does nearly what is present on that answer, but in a more open way. It not only works on iframes, but also on images, audio, video and whatever has a onload event!
Without further due, here's the code:
(function($){
$.fn.extend({allLoaded: function(fn){
if(!(fn instanceof Function))
{
throw new TypeError('fn must be a function');
}
var $elems = this;
var waiting = this.length;
var handler = function(){
--waiting;
if(!waiting)
{
setTimeout(fn.bind(window), 4);
}
};
return $elems.one('load.allLoaded', handler);
}});
})(window.jQuery);
It works by adding a load handler to every element in that selection. Since it is a plugin, you can use in whatever way you decide to use it.
Here's an example, that loads 30 random images:
//plugin code
(function($){
$.fn.extend({allLoaded: function(fn){
if(!(fn instanceof Function))
{
throw new TypeError('fn must be a function');
}
var $elems = this;
var waiting = this.length;
var handler = function(){
--waiting;
if(!waiting)
{
setTimeout(fn.bind(window), 4);
}
};
return $elems.one('load.allLoaded', handler);
}});
})(window.jQuery);
$(function(){
//generates the code for the 30 images
for(var i = 0, html = ''; i < 30; i++)
html += '<img data-src="http://lorempixel.com/g/400/200/?_=' + Math.random() + '">';
//stuffs the code into the body
$('#imgs').html(html);
//we select all images now
$('img')
.allLoaded(function(){
//runs when done
alert('loaded all')
})
.each(function(){
//the image URL is on a `data` attribute, to delay the loading
this.src = this.getAttribute('data-src')
})
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js"></script>
<div id="imgs"></div>
Your problem, as said before many times, is that you have a load event attached to your iframe. That event is fired everytime the content change.
After that, you set a new event on #iframe2. When it's content changes, it will fire events left and right, above and beyound what you wish!
The best aproach is to keep track of which ones you loaded or not. After all have been loaded, you simply run the function.
The problem is that you're waiting until #iframe1 loads before you attach a handler for #iframe2 loading. So if #iframe2 loads first, you'll never get your callback.
Instead, watch the load event on both of them and track which ones you've seen:
var seen1 = false,
seen2 = false;
$('#iframe1, #iframe2').load(function(){
if (this.id == "iframe1") {
seen1 = true;
} else {
seen2 = true;
}
if (seen1 && seen2) {
alert('loaded!');
}
});
Why do you expect 2nd iframe to load after the first one?
~function () {
var loaded = 0;
$('#iframe1, #iframe2').load(function (){
if (++loaded === 2) {
alert('loaded!');
}
});
}()

JS/JQuery Retry Img Load After 1 Second

I have a request calling up a bunch of images like so:
<a href='www.domain1.com'><img src='../image/img1.png' onerror='imgError(this);'/></a>
<a href='www.domain2.com'><img src='../image/img2.png' onerror='imgError(this);'/></a>
The problem is when the call is made some of the images (~20%) are not ready yet. They need another second.
So in js or jquery what I would like to do is on error get the images that failed, wait 1 second, then try to load those failed images again. If they fail on the 2nd try -- oh well, Im okay with that. But Im not doing this correctly... Should I not be calling a timeout inside of another method in js?
function imgError(image) {
image.onerror = "";
image.src = image;
setTimeout(function () {
return true;
}, 1000);
}
Add a cache breaker.
function imgError(image) {
image.onerror = null;
setTimeout(function (){
image.src += '?' + +new Date;
}, 1000);
}
(This assumes your image URL doesn't already have a query string, per the example. If it does, a little more work is obviously required.)

JS doesn't fire in IE does in Chrome

The following code doesn't seem to work in IE (using 9) - Test 1 fires but test 2 does not. Is there a different way of doing this for IE?
<script>
$(document).ready(function () {
overlay = $("#overlay");
img = $("#myimg");
alert('test 1');
img.load(function () {
alert('test 2');
var myPercent = 50;
var myHeight = $("#myimg").height() / 100 * myPercent;
overlay.height(myHeight);
overlay.width($("#myimg").width());
$(".percent").css('margin-top', $("#myimg").height()/2 - $(".percent").height()/2);
$(".percent").text(myPercent + "%");
});
});
</script>
EDIT: Fixed it by using $(window).load(function() { instead of document ready and removing the function
From the jQuery API of .load()
Caveats of the load event when used with images
A common challenge developers attempt to solve using the .load()
shortcut is to execute a function when an image (or collection of
images) have completely loaded. There are several known caveats with
this that should be noted. These are:
It doesn't work consistently nor reliably cross-browser
It doesn't fire correctly in WebKit if the image src is set to the
same src as before
It doesn't correctly bubble up the DOM tree Can cease to fire for
images that already live in the browser's cache
Make use of $(window).load():
$(window).load(function() {
alert('test 2');
var myPercent = 50;
var myHeight = $("#myimg").height() / 100 * myPercent;
overlay.height(myHeight);
overlay.width($("#myimg").width());
$(".percent").css('margin-top', $("#myimg").height()/2 - $(".percent").height()/2);
$(".percent").text(myPercent + "%");
});
Technically you should be able to add onload parameter on img tag which calls js function that does the same thing. I haven't tested this but it's worth a shot.
...
<script>
function resizeImg ($img) {
// code
}
</script>
...
<img onload="resizeImg($(this));" src="foo.png" />
You might as well use jQuery then.
...
<script>
$("#fooImg").load(function () {
// code
});
</script>
...
<img id="fooImg" src="foo.png" />
If you don't mind using a plugin, Paul Irish made a simple plugin to handle image loading, seeing as .load() does not handle images. The plugin was moved and turned into a project which can be found on GitHub.
$.fn.imagesLoaded = function (callback) {
var elems = this.filter('img'),
len = elems.length,
// data uri bypasses webkit log warning (thx doug jones (cjboco))
blank = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
elems.bind('load', function () {
// check image src to prevent firing twice (thx doug jones (cjboco))
if (--len <= 0 && this.src !== blank) {
callback.call(elems, this);
}
}).each(function () {
// cached images don't fire load sometimes, so we reset src.
if (this.complete || this.complete === undefined) {
var src = this.src;
// webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
this.src = blank;
this.src = src;
}
});
};
You can see it in action at this fiddle.
http://jsfiddle.net/denniswaltermartinez/c4DMH/
img.load() as an event is not supported by IE. Actually it is not necessary, that $(document).ready() function has already ensure img and other elements loaded. Just remove it.

image.onload event and browser cache

I want to create an alert box after an image is loaded, but if the image is saved in the browser cache, the .onload event will not be fired.
How do I trigger an alert when an image has been loaded regardless of whether the image has been cached or not?
var img = new Image();
img.src = "img.jpg";
img.onload = function () {
alert("image is loaded");
}
As you're generating the image dynamically, set the onload property before the src.
var img = new Image();
img.onload = function () {
alert("image is loaded");
}
img.src = "img.jpg";
Fiddle - tested on latest Firefox and Chrome releases.
You can also use the answer in this post, which I adapted for a single dynamically generated image:
var img = new Image();
// 'load' event
$(img).on('load', function() {
alert("image is loaded");
});
img.src = "img.jpg";
Fiddle
If the src is already set then the event is firing in the cached case before you even get the event handler bound. So, you should trigger the event based off .complete also.
code sample:
$("img").one("load", function() {
//do stuff
}).each(function() {
if(this.complete || /*for IE 10-*/ $(this).height() > 0)
$(this).load();
});
There are two possible solutions for these kind of situations:
Use the solution suggested on this post
Add a unique suffix to the image src to force browser downloading it again, like this:
var img = new Image();
img.src = "img.jpg?_="+(new Date().getTime());
img.onload = function () {
alert("image is loaded");
}
In this code every time adding current timestamp to the end of the image URL you make it unique and browser will download the image again
I have met the same issue today. After trying various method, I realize that just put the code of sizing inside $(window).load(function() {}) instead of document.ready would solve part of issue (if you are not ajaxing the page).
I found that you can just do this in Chrome:
$('.onload-fadein').each(function (k, v) {
v.onload = function () {
$(this).animate({opacity: 1}, 2000);
};
v.src = v.src;
});
Setting the .src to itself will trigger the onload event.

Categories

Resources