Bug in my lazyload plugin for mootools - javascript

I want to implement a plug-in serial download pictures in MooTools. Let's say there are pictures with the img tag inside a div with the class imageswrapper. Need to consistently download each image after it loads the next and so on until all the images are not loaded.
window.addEvent('domready', function(){
// get all images in div with class 'imageswrapper'
var imagesArray = $$('.imageswrapper img');
var tempProperty = '';
// hide them and set them to the attribute 'data-src' to cancel the background download
for (var i=0; i<imagesArray.length; i++) {
tempProperty = imagesArray[i].getProperty('src');
imagesArray[i].removeProperty('src');
imagesArray[i].setProperty('data-src', tempProperty);
}
tempProperty = '';
var iterator = 0;
// select the block in which we will inject Pictures
var injDiv = $$('div.imageswrapper');
// recursive function that executes itself after a new image is loaded
function imgBomber() {
// exit conditions of the recursion
if (iterator > (imagesArray.length-1)) {
return false;
}
tempProperty = imagesArray[iterator].getProperty('data-src');
imagesArray[iterator].removeProperty('data-src');
imagesArray[iterator].setProperty('src', tempProperty);
imagesArray[iterator].addEvent('load', function() {
imagesArray[iterator].inject(injDiv);
iterator++;
imgBomber();
});
} ;
imgBomber();
});

There are several issues I can see here. You have not actually said what the issue is so... this is more of a code review / ideas for you until you post the actual problems with it (or a jsfiddle with it)
you run this code in domready where the browser may have already initiated the download of the images based upon the src property. you will be better off sending data-src from server directly before you even start
Probably biggest problem is: var injDiv = $$('div.imageswrapper'); will return a COLLECTION - so [<div.imageswrapper></div>, ..] - which cannot take an inject since the target can be multiple dom nodes. use var injDiv = document.getElement('div.imageswrapper'); instead.
there are issues with the load events and the .addEvent('load') for cross-browser. they need to be cleaned up after execution as in IE < 9, it will fire load every time an animated gif loops, for example. also, you don't have onerror and onabort handlers, which means your loader will stop at a 404 or any other unexpected response.
you should not use data-src to store the data, it's slow. MooTools has Element storage - use el.store('src', oldSource) and el.retrieve('src') and el.eliminate('src'). much faster.
you expose the iterator to the upper scope.
use mootools api - use .set() and .get() and not .getProperty() and .setProperty()
for (var i) iterators are unsafe to use for async operations. control flow of the app will continue to run and different operations may reference the wrong iterator index. looking at your code, this shouldn't be the case but you should use the mootools .each(fn(item, index), scope) from Elements / Array method.
Anyway, your problem has already been solved on several layers.
Eg, I wrote pre-loader - a framework agnostic image loader plugin that can download an array of images either in parallel or pipelined (like you are trying to) with onProgress etc events - see http://jsfiddle.net/dimitar/mFQm6/ - see the screenshots at the bottom of the readme.md:
MooTools solves this also (without the wait on previous image) via Asset.js - http://mootools.net/docs/more/Utilities/Assets#Asset:Asset-image and Asset.images for multiple. see the source for inspiration - https://github.com/mootools/mootools-more/blob/master/Source/Utilities/Assets.js
Here's an example doing this via my pre-loader class: http://jsfiddle.net/dimitar/JhpsH/
(function(){
var imagesToLoad = [],
imgDiv = document.getElement('div.injecthere');
$$('.imageswrapper img').each(function(el){
imagesToLoad.push(el.get('src'));
el.erase('src');
});
new preLoader(imagesToLoad, {
pipeline: true, // sequential loading like yours
onProgress: function(img, imageEl, index){
imgDiv.adopt(imageEl);
}
});
}());

Related

know when specific resource finish loading

sorry if this thread is repited but i didn't find it in browser.
I want to create/handle an event that let me know when a specific resource load into the web page. (similar to onload or DOMContentLoaded, BUT i need to know when each resource finish, not the whole page)
Example--> Assets = image1,image2,image3,...
when finish loading image1 trigger a ready event,
After ready event triggered start loading image2
keep going...keep going....
After last image, trigger again ready event.
The final result is the same as DOMContentLoaded or "windows.onload", but as i said im not abble to put some logic in the middle of assets load.
Someone know how to handle that kind of thing?
I am not sure, why (or if) you want to 'load' the images in a specific order. Be aware that browsers rarely open a single connection. Normally all resources will be collected (after downloading the html itself) and the browser will load many of them in parallel. In short - in case you do this for performance or speed, you would slow down the process!
A more common approach to lazy load images is using the viewport / scroll position to decide which images should be loaded "next", there are a few jquery plugins, e.g. lazyload.
Anyway - in case you do not care about the order and you just want to a element specific callback when ready you could do something like this:
$("img").one("load", function() {
var $this = this;
// 'this' is your specific element
// do whatever you like to do onready
}).each(function() {
// handle cached elements
if(this.complete) $(this).load();
});
In case you do care about the order and you really want to load the next image after the first one is ready you need a different approach.
First: Your HTML does not contain image sources, but images with a data-attribute(s):
<img data-src="the/path/to/your/image" data-assets-order="1" />
Second: In JS you collect all these images without a real source, you order the collection and finally trigger the loading one after each other.
var toLoad = [];
// scanning for all images with a data-src attribute
// and collect them in a specified order.
$('img[data-src]').each(function() {
// either you define a custom order by a data-attribute (data-assets-order)
// or you use the DOM position as the index. Mixing both might be risky.
var index = $(this).attr('data-assets-order') ?
$(this).attr('data-assets-order') : toLoad.length;
// already existing? put the element to the end
if (toLoad[index]) { index = toLoad.length; }
toLoad[index] = this;
});
// this method handles the loading itself and triggers
// the next element of the collection to be loaded.
function loadAsset(index) {
if (toLoad[index]) {
var asset = $(toLoad[index]);
// bind onload to the element
asset.on("load", function() {
// in case it is ready, call the next asset
if (index < toLoad.length) {
loadAsset(index + 1);
}
});
// set the source attribut to trigger the load event
asset.attr('src', asset.attr('data-src'));
}
}
// we have assets? start loading process
if (toLoad.length) { loadAsset(index); }
I'm not sure if this will work for you, but did you tried the setTimeOut function?
You can set a specific time for your image to load (through ajax, you can make the call to the resource and wait until request.done), and after that, call to the next one.
I also found something 'similar' to your request here:
How to wait for another JS to load to proceed operation?
Sorry if this doesn't help you.

Image loading time

When I click on my website sometimes you can visually see the images slowly loading into the place. I don't really like this and if possible would like to prevent it from happening.
From reading round it seems preloading images is the solution I'm looking for? Let me know if that's correct or if there is a better way.
On this forum I see lots of answers to preloading images and below is a code I think works but I want to change it slightly:
var preloadImages = [ img/1.jpg,img/2.jpg];
for(var i = 0 ; i < preloadImages.length; i++) {
new Image().src = preloadImages[i];
}
I think this code above works but it requires me to type in every image source into an array. On my website there are lots of images and I will probably continue to add more. So is there a way to push all the image sources into the array without actually typing them in. So as I add more images the preloading take care of itself.
If you wanted to go down the route you already have you could search the entire DOM for tags first and then add them to the array.
Perhaps something along these lines:
images = []
$(body).find('img').each ->
imgSrc = $(this).attr('src')
image.push imgSrc
i = 0
while i < preloadImages.length
(new Image).src = preloadImages[i]
i++
That will need jQuery and probably some tweaking. I can't imagine this being the best approach however as you still need to wait for the DOM, then jQuery to load and then run through the entire DOM with the function(s).
If the issue is more that the content is moving around you can (and probably should) prevent this by giving the tag itself a height and width if you know it in advance.
Additionally it's always worth optimizing your images (either by hand or on build/compile using something like Gulp.js) if you haven't already. It's incredible how much you can reduce load times by simply by using the optimal image (in terms of size and format).
You can call below function after page load
function preloadImages(){
$('img').each(function(){
new Image().src = this.src;
});
}
You can use this function to set multiple after-page-load functions
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = function(){
func();
};
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
};
}
}
then add your function like this
addLoadEvent(preloadImages);
But I don't think you can really pre-load images like this. To really pre-load images, you have to start loading those images as soon as your DOM starts to render on the client-side. And that means loading them in the head section. But if you do that, because you don't have your html just yet, you will not be able to run the above function, because it needs the DOM to be fully downloaded. You have to hardcode your image sources into javascript like you already did, and put your function as the first thing in the head section.

PhantomCSS/CasperJS - Greying out advertisement images

Hey guys just testing our pages out using the grunt-phantomcss plugin (it's essentially a wrapper for PhantomJS & CasperJS).
We have some stuff on our sites that comes in dynamically (random profile images for users and random advertisements) sooo technically the page looks different each time we load it, meaning the build fails. We would like to be able to jump in and using good ol' DOM API techniques and 'grey out'/make opaque these images so that Casper/Phantom doesn't see them and passes the build.
We've already looked at pageSettings.loadImages = false and although that technically works, it also takes out every image meaning that even our non-ad, non-profile images get filtered out.
Here's a very basic sample test script (doesn't work):
casper.start( 'http://our.url.here.com' )
.then(function(){
this.evaluate(function(){
var profs = document.querySelectorAll('.profile');
profs.forEach(function( val, i ){
val.style.opacity = 0;
});
return;
});
phantomcss.screenshot( '.profiles-box', 'profiles' );
});
Would love to know how other people have solved this because I am sure this isn't a strange use-case (as so many people have dynamic ads on their sites).
Your script might actually work. The problem is that profs is a NodeList. It doesn't have a forEach function. Use this:
var profs = document.querySelectorAll('.profile');
Array.prototype.forEach.call(profs, function( val, i ){
val.style.opacity = 0;
});
It is always a good idea to register to page.error and remote.message to catch those errors.
Another idea would be to employ the resource.requested event handler to abort all the resources that you don't want loaded. It uses the underlying onResourceRequested PhantomJS function.
casper.on("resource.requested", function(requestData, networkRequest){
if (requestData.url.indexOf("mydomain") === -1) {
// abort all resources that are not on my domain
networkRequest.abort();
}
});
If your page handles unloaded resources well, then this should be a viable option.

Dynamically adding HTML with Javascript which has <img> tags. How do I tell when the images are loaded

Lets say I have a div, and want to inject into that div some HTML. The HTML will include 1 or more images.
Is there any way with plain JavaScript or jQuery to determine when the images have loaded?
So if I do the following, can you place an event listener onto a single element to tell when the contents, including images, is ready?:
var html = "<p>Some text <img src='image.jpg' /></p><p>Some more text <img src='image2.jpg' /></p>";
$("div.output").html(html);
The images will be different every time so I cannot preload using JavaScript image object.
One method I'm thinking of doing is running through the HTML with regular expressions or jQuery, finding all image URLs, loop through them and preload each with the image object. When all have been preloaded, then inject the HTML into the output div.
Unless you hook up an onload event to each image before it loads and count them up, there's no easy way to tell when they're all loaded.
Here's a relatively bulletproof method (demo) that uses Deferreds, tested and working in IE 6-9, Chrome 19, and Firefox 3.6-10, Opera 10.10-11.52, Android 4, and iOS 5.
First, we'll write a small jQuery plugin that returns an array of Deferreds for each element in the jQuery collection. Each Deferred will be resolved when the element loads; or rejected if the element fails to load or (optionally) takes longer than timeout seconds.
$.fn.loaded = function(opts) {
var o = $.extend({timeout:10000}, opts) // Merge default options with supplied options
, r = []; // Return value
this.each(function() {
var dfd = new $.Deferred(), el = $(this), to;
if (o.timeout) to = setTimeout(function() {
done();
dfd.reject();
}, o.timeout);
el.bind('load.dfdl', function() {
done();
dfd.resolve();
}).bind('error.dfdl', function() {
done();
dfd.reject();
});
function done() { // internal clean-up
clearTimeout(to);
el.unbind('.dfdl');
}
r.push(dfd.promise());
});
return r;
};
The timeout will guard against cases where the browser never actually fires any events. I've set the default timeout to 10 seconds here; in the real world you might want to reduce that.
Now we'll generate 10 randomly-sized placekittens to use as images in the example.
var imgs=[];
for (var i = 0; i < 10; i++) imgs.push('<img src="http://placekitten.com/' + rnd() + '/' + rnd() + '"> ');
$('#imgs').html(imgs.join());
Finally, we'll put everything together with some magic:
$.when.apply($, $('#imgs img').loaded({timeout:10000}) ).done(function() {
alert('loaded successfully');
}).fail(function() {
alert('load failed or timed out');
});
$.when creates a master Deferred that gets resolved only when all of its child Deferreds resolve, or rejects when a child rejects. It normally expects you to pass each Deferred as an argument (it does not support passing in an array of Deferreds), so we have to apply our array. (I might do $.whenall = function(dfds) { $.when.apply($,dfds); }; so that your app code has a cleaner $.whenall( $('#imgs img').loaded() )...)
Try this:
$("#preload_div").html(
'<img src="image.png" alt="preload" class="preload" width="0" height="0">'
);
The event handler can be bound to the image:
$('preload').load(function () {
var html = "<p>Some text <img src='image.jpg' /></p><p>Some more text <img src='image2.jpg' /></p>";
$("div.output").html(html);
});
Of course, you would have to incorporate an .each() for each image to loop through all images.
EDIT: On one of my AJAX websites, I preload the next image I need, so when a user clicks the 'next' button, the image that the new content needs is already cached. See http://funl.es/p/1
One method I'm thinking of doing is running through the HTML with regular expressions or jQuery, finding all image URLs, loop through them and preload each with the image object. When all have been preloaded, then inject the HTML into the output div.
var imagesLoaded = [];
var imagesErrored = [];
$('img').each(function() {
$(this).load(function() {
imagesLoaded.push($(this).attr("src"));
if( (imagesLoaded.length + imagesErrored.length) == $('img').length ) {
displayResults(imagesLoaded, imagesErrored);
}
});
$(this).error(function() {
imagesErrored.push($(this).attr("src"));
if( (imagesLoaded.length + imagesErrored.length) == $('img').length ) {
displayResults(imagesLoaded, imagesErrored);
}
});
function displayResults(imagesLoaded, imagesErrored) {
for(var i = 0; i < imagesLoaded.length; i++) {
alert(imagesLoaded[i] + " has loaded successfully.");
$('#myDiv').append("<img src='" + imagesLoaded[i] + "' />");
}
for(var i = 0; i < imagesErrored.length; i++) {
alert(imagesLoaded[i] + " did NOT load successfully.");
}
}
The above loops through all images and checks whether or not they loaded, using the load event. Using the error event, we also get a collection of all images that for whatever reason did not load correctly. After all images have been preloaded, we can then inject them onto the page in the "displayResults" function. This is based loosely on the last paragraph in your question.
However, it doesn't use regular expressions, as they are not the right tool for manipulating the DOM. Fortunately, JavaScript and jQuery provide us with the tools we need to perform DOM manipulations and parsing activities.
Finally, from your question, it sounded like you may have been interested in knowing what images loaded successfully. The above collections that are built from the load and error events will provide you with those details, should you need them.
I think you should bind the load handler after you insert the images into the html.
DEMO
//Add image
$('#result').append('<img src= "<Your_Image_URL>" width="300" height="300"/>');
//Bind handler also add condition to not bind images that was not added before.
$('#result').find('img').not('.loaded').on('load', function () {
alert('Image Loaded');
//Add code that you want to do after the image is added
}).addClass('loaded');
Also please be aware of 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
Reference: http://api.jquery.com/load-event/
Have you considered using the jQuery 'on' event mechanism? Using 'on' should allow you to automatically bind 'load' handlers to each image as it gets dynamically added to the container and DOM.
Link: jQuery docs for'on' function
If you're using pre 1.7 jQuery, you'll need to use the 'live' function since 'on' was added with the 1.7 release.
Link: jQuery docs for 'live' function (deprecated)
I think that there is no easy way to do that. The thing that you could do is to load all the images on a div that is hidden ( display: none ).
When you want to show the pictures you can do:
$("#myDiv").show();

How does the javascript preloading work?

I don't want to know a way to preload images, I found much on the net, but I want to know how it works.
How is javascript able to preload images?
I mean, I tried a snippet from here, and even if it works, it doesn't seem to preload images.
When I check firebug, I can see that the image is loaded twice, once while the preloading, another time when displaying it!
To improve this code I'd like to know how it works.
Here is what i do:
function preload(arrayOfImages) {
$(arrayOfImages).each(function(){
$('<img/>')[0].src = this;
//(new Image()).src = this;
alert(this +' && ' + i++);
});
}
then i do something like that:
preloader = function() {
preload(myImages);
}
$(document).ready(preloader);
Here is how i display/add the image :
$("li.works").click(function() {
$("#viewer").children().empty();
$('#viewer').children().append('<img src=\'images/ref/'+this.firstChild.id+'.jpg\' alt="'+this.firstChild.id+'" \/>')
$("#viewer").children().fadeIn();
Your basic Javascript preloader does this:
var image = new Image();
image.src = '/path/to/the/image.jpg';
The way it works is simply by creating a new Image object and setting the src of it, the browser is going to go grab the image. We're not adding this particular image to the browser, but when the time comes to show the image in the page via whatever method we have setup, the browser will already have it in its cache and will not go fetch it again. I can't really tell you why whatever you have isn't working this way without looking at the code, though.
One interesting gotcha that is discussed in this question is what happens when you have an array of images and try preloading them all by using the same Image object:
var images = ['image1.jpg','image2.jpg'];
var image = new Image();
for(var x = 0; x < images.length; x++) {
image.src = images[x];
}
This will only preload the last image as the rest will not have time to preload before the loop comes around again to change the source of the object. View an example of this. You should be able to instantly see the second image once you click on the button, but the first one will have to load as it didn't get a chance to preload when you try to view it.
As such, the proper way to do many at once would be:
var images = ['image1.jpg','image2.jpg'];
for(var x = 0; x < images.length; x++) {
var image = new Image();
image.src = images[x];
}
Javascript preloading works by taking advantage of the caching mechanism used by browsers.
The basic idea is that once a resource is downloaded, it is stored for a period of time locally on the client machine so that the browser doesn't have to retrieve the resource again from across the net, the next time it is required for display/use by the browser.
Your code is probably working just fine and you're just misinterpeting what Fire Bug is displaying.
To test this theory just hit www.google.com with a clean cache. I.e. clear your download history first.
The first time through everything will likely have a status of 200 OK. Meaning your browser requested the resource and the server sent it. If you look at the bottom on the Fire Bug window it will says how big the page was say 195Kb and how much of that was pulled from cache. In this case 0Kb.
Then reload the same page without clearing your cache, and you will still see the same number of requests in FireBug.
The reason for this is simple enough. The page hasn't changed and still needs all the same resources it needed before.
What is different is that for the majority of these requests the server returned a 304 Not Modified Status, so the browser checked it's cache to see if it already had the resource stored locally, which in this case it did from the previous page load. So the browser just pulled the resource from the local cache.
If you look at the bottom of the Fire Bug window you will see that page size is still the same (195Kb) but that the majority of it, in my case 188Kb, was pulled locally from cache.
So the cache did work and the second time i hit Google I saved 188Kb of download.
I'm sure you will find the same thing with preloading your images. The request is still made but if the server returns a status of 304 then you will see that the image is in fact just pulled from local cache and not the net.
So with caching, the advantage is NOT that you kill off all future resource requests, i.e. a Uri lookup is still made to the net but rather that if possible the browser will pull from the local cache to satisify the need for the content, rather than run around the net looking for it.
You may be confused by the concept of "preloading". If you have a bunch of images in your HTML with <img src="...">, they cannot be preloaded with Javascript, they just load with the page.
Preloading images with Javascript is about loading images not already in the document source, then displaying them later. They are loaded after the page has rendered for the first time. They are preloaded in order to eliminate/minimize loading time when it comes to making them appear, for example when changing an image on mouse rollover.
For most applications, it is usually better practice to use "CSS sprites" as a form of preloading, in lieu of Javascript. SO should have a ton of questions on this.
It just involves making a new DOM image object and setting the src attribute. Nothing clever and AFAIK, it has always worked for me.
Is it possible the second "load" firebug is showing you is it loading it from cache?
The index on the loop is only looking
at the first image. Change it to use
the index:
function preload(arrayOfImages) {
$(arrayOfImages).each(function(i){ // Note the argument
$('<img/>')[i].src = this; // Note the i
//(new Image()).src = this;
alert(this +' && ' + i++);
});
}
Edit: In retrospect, this was wrong and I can see you're trying to create image elements. I don't understand why the index is there at all, there need not be an index. I think the function should look like this:
function preload(arrayOfImages) {
$(arrayOfImages).each(function () {
$('<img/>').attr('src', this);
});
}
And to instantiate it, why not just do this:
$(function () { // Equivalent to $(document).ready()
preload(myImages);
});
JavaScript image preloading works because when a DOM element that contains an image is created, the image is downloaded and cached. Even if another request is made when the image is actually rendered from the HTML, the server will send back a 304 (not changed), and the browser will simply load the image from its cache.
Paolo suggests using the following notation to create an image object:
var image = new Image();
While this will work, the DOM-compliant way of doing this is:
var image = document.createElement('img');
image.setAttribute('src', 'path/to/image.jpg');
Which is the way it is being done in the script, except it's using jQuery's HTML string literal syntax to do it. Additionally, most modern browsers offer compatibility with the Image() constructor by simply calling DOM-standard methods. For example, if you open up the Google Chrome JavaScript console and type Image, this is what you'll get:
function Image() {
return document.createElementNS('http://www.w3.org/1999/xhtml', 'img');
}
Chrome merely uses the native DOM methods to create an image element.

Categories