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.
Related
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.
I'm going through a book called "Javascript & jQuery" by Jon Duckett. In it, there's an example of a photo viewer that can be found here:
http://javascriptbook.com/code/c11/photo-viewer.html
In the book, he states that
because larger images take longer to load, if a user clicks on a two different images in quick succession: 1) The second image could load faster than the first one and be displayed in the browser. 2) It would be replaced by the first image the user clicked on when that image had loaded. This could make users think the wrong image has loaded.
I get this concept, however, the code he cites does not seem to fix the problem, at least not in my eyes. Here is an abbreviated version of the full script, which can be found here: http://javascriptbook.com/code/c11/js/photo-viewer.js (his version is much better commented)
var request;
var $current;
var cache = {};
var $frame = $('#photo-viewer');
var $thumbs = $('.thumb');
// Other code goes here...
$(document).on('click', '.thumb', function(e){ // User clicks on thumbnail
var $img;
var src = this.href;
request = src;
// Other code goes here...
$img = $('<img/>');
cache[src] = {
$img: $img,
isLoading: true
};
$img.on('load', function() { // Code to run when image loads
$img.hide();
$frame.removeClass('is-loading').append($img);
cache[src].isLoading = false;
if (request === src) { // Check to make sure the image that is loading is the most recently requested image
crossfade($img); // Call function to load new image
}
});
$img.attr({
'src': src,
'alt': this.title || ''
});
So basically his solution is to create a global "request" variable and a local "src" variable. When a user clicks on a thumbnail, the request variable is assigned the value of src, which is the path to the image being requested. Later on, when the image loads, that request variable is checked to ensure that it equals src.
The logic behind the line that performs the check seems faulty to me. However, I'm new to programming and I'm not sure if there's a concept I'm not fully understanding. The line in question is this one:
if (request === src) {
Since the request variable was assigned the value of the src variable earlier in the script, they would still be the same, even when an old image loads after the new one has been clicked. The only difference is that value of each would be different.
Wouldn't the following be the right way to check this?
if (request === this.src) {
That way if the old image loads after the most recently requested image, the request variable would have stored the most recent path, and the old image's src attribute would no longer match it.
This might be pretty confusing, since I didn't include the whole script, so let me know if you need any elaboration. I tried to only include the absolutely critical aspects of the code so that you wouldn't have to go through all of it to understand my question.
Thank you for any help!
The code in the book is correct. The trick here is to have a variable across all instances of the callback that contains the most recently clicked href.
|---1----------...loading...------------cb------------|
|---2--...loading...--cb------------------------------|
|---3-----...loading...-----------cb------------------|
If this were the timeline of three clicks...we click a large image first (longer loading time before callback (cb)) and then we click a second, smaller image, finally followed by a medium image. We would have three click events all firing at the same time. The first callback we'd get is the 2nd click, followed by the third click, followed by the first click. However, since our request variable is outside the scope of our callback, it's value will be equal to the last thing it was set to, in this case, the href of click three.
I think all this logic makes sense to you but it's the timing you're not understanding. When click one happens, we set the external variable request to href1, when click two happens, we set it to href2 and then finally click three sets it to href3. These things all happened prior to the .on('load') callback which means, when image two loads first and we check the value of request, it's already been set to href3 which is !== src (href2), once image 3 loads next in the timeline, we see that this is the image we expect so we load it. Some few odd seconds/minutes later when image one calls it's callback, we see that request is not the href for image 1 and we don't show image one.
Either your code or the suggested code should work. The key you are missing is the scopes of the two variables.
var request;
$(document).on('click', '.thumb', function(e){ // User clicks on thumbnail
var $img;
var src = this.href; //variable exists only inside this onclick callback
request = src;
request exists outside of the enclosure so every time an image is clicked, its value gets overridden and stored.
src is declared inside of the onclick callback function and initialized with the link href. If another image is clicked while the first is loading, a new src variable is created to store the new data. As far as the onload callback is concerned, src doesn't change at that scope. src remains the value that it was first set at (the href of the link) and isn't overridden by following clicks.
I have script that I would like visitors on my website to run when they load a web page. It looks like this:
window.onload = function(){
var pxl=document.createElement('img');
pxl.setAttribute('src', 'http://localhost:8080/getTrackingPixel')
document.body.appendChild(pxl);
}
Most of the times the source returns an image and it works fine. However, sometimes it returns this:
<html><body style="background-color:transparent"></body></html>
And I can't really change the fact that it might sometimes not return an image. How do I change the javascript so that it can handle the html response without any errors? It might be possible for me to predict when it happens though - but I haven't managed to find a good way to request the source and return the html either.
You can achieve it by using the javascript Image object which, unlike the createElement approach, allows you to fetch the src url before inserting the img in the DOM.
The onload event of the Image object won't fire if the loaded content isn't an img.
Here it is :
window.onload = function(){
var pxl = new Image();
pxl.onload = function(){
// is IMG
document.body.appendChild(pxl);
}
pxl.onerror = function(){
// is not IMG
// Meaning in your case : <html><body style="background-color:transparent"></body></html>
}
pxl.src = 'http://localhost:8080/getTrackingPixel';
}
(Note that your code also missed the semicolon ";" line 4)
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);
}
});
}());
I'm working on a website which uses ExpressionEngine to create a list of images with img1, img2, img3 etc as the ID and creates an array with their sources imgAddresses[1], imgAddresses[2], imgAddresses[3] etc.
I'm attempting to create a function which loads the first image, then (when the first is completely loaded), load the second, third etc. The following is what I have so far:
function loadImage(counter) {
var i = document.getElementById("img"+counter);
if(counter==imgAddresses.length) { return; }
i.onload = function(){
loadImage(counter+1)
};
i.src = imgAddresses[counter];
}
document.onload=loadImage(0);
It works when refreshing the page, but not when accessing the page via the URL. As far as I can tell from research, this is because the onload event is not fired when a cached image is loaded, and refreshing the page clears the cache, whereas accessing the page via the URL does not.
Research suggests that assigning the src of the image after declaring the onload event would get around this, but it does not seem to have solved it in this case. I was thinking that this may be because the onload event is recursive in this case.
Does anyone have any ideas on how to make sure the browser is loaded a fresh copy of the image, rather than a cached version? Or whether there is a better way to write this function? Thanks for any help!
EDIT: One solution that I have found is to change the img source assignment to:
i.src = imgAddresses[counter] + '?' + new Date().getTime();
This forces the user to load a fresh copy each time, which I guess is not so much a solution, but a workaround
The only thing I can say is that you are not attaching the document.onload handler correctly. I cannot tell if it will fix your issue because image.onload is not reliable in all browsers, however the onload should be set to a function reference and that's not what you are doing.
Instead, you should have something like:
function loadImage(counter) {
//initialize the counter to 0 if no counter was passed
counter = counter || 0;
var i = document.getElementById("img"+counter);
if(counter==imgAddresses.length) { return; }
i.onload = function(){
loadImage(counter+1)
};
i.src = imgAddresses[counter];
}
document.onload = loadImage; //instead of loadImage(0);
You can tell how the browser will manage the cached resources
Take a look to HTML5 cache approach:
HTML5: The cache manifest file
This way you can avoid the browser cache for the specified resources.