Is the function I wrote below enough to preload images in most, if not all, browsers commonly used today?
function preloadImage(url)
{
var img=new Image();
img.src=url;
}
I have an array of image URLs that I loop over and call the preloadImage function for each URL.
Yes. This should work on all major browsers.
Try this I think this is better.
var images = [];
function preload() {
for (var i = 0; i < arguments.length; i++) {
images[i] = new Image();
images[i].src = preload.arguments[i];
}
}
//-- usage --//
preload(
"http://domain.tld/gallery/image-001.jpg",
"http://domain.tld/gallery/image-002.jpg",
"http://domain.tld/gallery/image-003.jpg"
)
Source: http://perishablepress.com/3-ways-preload-images-css-javascript-ajax/
You can move this code to index.html for preload images from any url
<link rel="preload" href="https://via.placeholder.com/160" as="image">
In my case it was useful to add a callback to your function for onload event:
function preloadImage(url, callback)
{
var img=new Image();
img.src=url;
img.onload = callback;
}
And then wrap it for case of an array of URLs to images to be preloaded with callback on all is done:
https://jsfiddle.net/4r0Luoy7/
function preloadImages(urls, allImagesLoadedCallback){
var loadedCounter = 0;
var toBeLoadedNumber = urls.length;
urls.forEach(function(url){
preloadImage(url, function(){
loadedCounter++;
console.log('Number of loaded images: ' + loadedCounter);
if(loadedCounter == toBeLoadedNumber){
allImagesLoadedCallback();
}
});
});
function preloadImage(url, anImageLoadedCallback){
var img = new Image();
img.onload = anImageLoadedCallback;
img.src = url;
}
}
// Let's call it:
preloadImages([
'//upload.wikimedia.org/wikipedia/commons/d/da/Internet2.jpg',
'//www.csee.umbc.edu/wp-content/uploads/2011/08/www.jpg'
], function(){
console.log('All images were loaded');
});
const preloadImage = src =>
new Promise((resolve, reject) => {
const image = new Image()
image.onload = resolve
image.onerror = reject
image.src = src
})
// Preload an image
await preloadImage('https://picsum.photos/100/100')
// Preload a bunch of images in parallel
await Promise.all(images.map(x => preloadImage(x.src)))
CSS2 Alternative: http://www.thecssninja.com/css/even-better-image-preloading-with-css2
body:after {
content: url(img01.jpg) url(img02.jpg) url(img03.jpg);
display: none;
}
CSS3 Alternative: https://perishablepress.com/preload-images-css3/
(H/T Linh Dam)
.preload-images {
display: none;
width: 0;
height: 0;
background: url(img01.jpg),
url(img02.jpg),
url(img03.jpg);
}
NOTE: Images in a container with display:none might not preload.
Perhaps visibility:hidden will work better but I have not tested this. Thanks Marco Del Valle for pointing this out
I recommend you use a try/catch to prevent some possible issues:
OOP:
var preloadImage = function (url) {
try {
var _img = new Image();
_img.src = url;
} catch (e) { }
}
Standard:
function preloadImage (url) {
try {
var _img = new Image();
_img.src = url;
} catch (e) { }
}
Also, while I love DOM, old stupid browsers may have problems with you using DOM, so avoid it altogether IMHO contrary to freedev's contribution. Image() has better support in old trash browsers.
Working solution as of 2020
Most answers on this post no longer work - (atleast on Firefox)
Here's my solution:
var cache = document.createElement("CACHE");
cache.style = "position:absolute;z-index:-1000;opacity:0;";
document.body.appendChild(cache);
function preloadImage(url) {
var img = new Image();
img.src = url;
img.style = "position:absolute";
cache.appendChild(img);
}
Usage:
preloadImage("example.com/yourimage.png");
Obviously <cache> is not a "defined" element, so you could use a <div> if you wanted to.
Use this in your CSS, instead of applying the style attribute:
cache {
position: absolute;
z-index: -1000;
opacity: 0;
}
cache image {
position: absolute;
}
If you have tested this, please leave a comment.
Notes:
Do NOT apply display: none; to cache - this will not load the
image.
Don't resize the image element, as this will also affect the
quality of the loaded image when you come to use it.
Setting position: absolute to the image is necessary, as the image elements will eventually make it's way outside of the viewport - causing them to not load, and affect performance.
UPDATE
While above solution works, here's a small update I made to structure it nicely:
(This also now accepts multiple images in one function)
var cache = document.createElement("CACHE");
document.body.appendChild(cache);
function preloadImage() {
for (var i=0; i<arguments.length; i++) {
var img = new Image();
img.src = arguments[i];
var parent = arguments[i].split("/")[1]; // Set to index of folder name
if ($(`cache #${parent}`).length == 0) {
var ele = document.createElement("DIV");
ele.id = parent;
cache.appendChild(ele);
}
$(`cache #${parent}`)[0].appendChild(img);
console.log(parent);
}
}
preloadImage(
"assets/office/58.png",
"assets/leftbutton/124.png",
"assets/leftbutton/125.png",
"assets/leftbutton/130.png",
"assets/leftbutton/122.png",
"assets/leftbutton/124.png"
);
Preview:
Notes:
Try not to keep too many images preloaded at the same time (this can cause major performance issues) - I got around this by hiding images, which I knew wasn't going to be visible during certain events. Then, of course, show them again when I needed it.
This approach is a little more elaborate. Here you store all preloaded images in a container, may be a div. And after you could show the images or move it within the DOM to the correct position.
function preloadImg(containerId, imgUrl, imageId) {
var i = document.createElement('img'); // or new Image()
i.id = imageId;
i.onload = function() {
var container = document.getElementById(containerId);
container.appendChild(this);
};
i.src = imgUrl;
}
Try it here, I have also added few comments
Solution for ECMAScript 2017 compliant browsers
Note: this will also work if you are using a transpiler like Babel.
'use strict';
function imageLoaded(src, alt = '') {
return new Promise(function(resolve) {
const image = document.createElement('img');
image.setAttribute('alt', alt);
image.setAttribute('src', src);
image.addEventListener('load', function() {
resolve(image);
});
});
}
async function runExample() {
console.log("Fetching my cat's image...");
const myCat = await imageLoaded('https://placekitten.com/500');
console.log("My cat's image is ready! Now is the time to load my dog's image...");
const myDog = await imageLoaded('https://placedog.net/500');
console.log('Whoa! This is now the time to enable my galery.');
document.body.appendChild(myCat);
document.body.appendChild(myDog);
}
runExample();
You could also have waited for all images to load.
async function runExample() {
const [myCat, myDog] = [
await imageLoaded('https://placekitten.com/500'),
await imageLoaded('https://placedog.net/500')
];
document.body.appendChild(myCat);
document.body.appendChild(myDog);
}
Or use Promise.all to load them in parallel.
async function runExample() {
const [myCat, myDog] = await Promise.all([
imageLoaded('https://placekitten.com/500'),
imageLoaded('https://placedog.net/500')
]);
document.body.appendChild(myCat);
document.body.appendChild(myDog);
}
More about Promises.
More about "Async" functions.
More about the destructuring assignment.
More about ECMAScript 2015.
More about ECMAScript 2017.
The HTML Living Standard now supports 'preload'
According to the W3C HTML spec you can now preload using JavaScript like so:
var link = document.createElement("link");
link.rel = "preload";
link.as = "image";
link.href = "https://example.com/image.png";
document.head.appendChild(link);
Here is my approach:
var preloadImages = function (srcs, imgs, callback) {
var img;
var remaining = srcs.length;
for (var i = 0; i < srcs.length; i++) {
img = new Image;
img.onload = function () {
--remaining;
if (remaining <= 0) {
callback();
}
};
img.src = srcs[i];
imgs.push(img);
}
};
Yes this will work, however browsers will limit(between 4-8) the actual calls and thus not cache/preload all desired images.
A better way to do this is to call onload before using the image like so:
function (imageUrls, index) {
var img = new Image();
img.onload = function () {
console.log('isCached: ' + isCached(imageUrls[index]));
*DoSomething..*
img.src = imageUrls[index]
}
function isCached(imgUrl) {
var img = new Image();
img.src = imgUrl;
return img.complete || (img .width + img .height) > 0;
}
I can confirm that the approach in the question is sufficient to trigger the images to be downloaded and cached (unless you have forbidden the browser from doing so via your response headers) in, at least:
Chrome 74
Safari 12
Firefox 66
Edge 17
To test this, I made a small webapp with several endpoints that each sleep for 10 seconds before serving a picture of a kitten. Then I added two webpages, one of which contained a <script> tag in which each of the kittens is preloaded using the preloadImage function from the question, and the other of which includes all the kittens on the page using <img> tags.
In all the browsers above, I found that if I visited the preloader page first, waited a while, and then went to the page with the <img> tags, my kittens rendered instantly. This demonstrates that the preloader successfully loaded the kittens into the cache in all browsers tested.
You can see or try out the application I used to test this at https://github.com/ExplodingCabbage/preloadImage-test.
Note in particular that this technique works in the browsers above even if the number of images being looped over exceeds the number of parallel requests that the browser is willing to make at a time, contrary to what Robin's answer suggests. The rate at which your images preload will of course be limited by how many parallel requests the browser is willing to send, but it will eventually request each image URL you call preloadImage() on.
The browser will work best using the link tag in the head.
export function preloadImages (imageSources: string[]): void {
imageSources
.forEach(i => {
const linkEl = document.createElement('link');
linkEl.setAttribute('rel', 'preload');
linkEl.setAttribute('href', i);
linkEl.setAttribute('as', 'image');
document.head.appendChild(linkEl);
});
}
This is what I did, using promises:
const listOfimages = [
{
title: "something",
img: "https://www.somewhere.com/assets/images/someimage.jpeg"
},
{
title: "something else",
img: "https://www.somewhere.com/assets/images/someotherimage.jpeg"
}
];
const preload = async () => {
await Promise.all(
listOfimages.map(
(a) =>
new Promise((res) => {
const preloadImage = new Image();
preloadImage.onload = res;
preloadImage.src = a.img;
})
)
);
}
This is the original answer but a with a more modern ES syntax:
let preloadedImages = [];
export function preloadImages(urls) {
preloadedImages = urls.map(url => {
let img = new Image();
img.src = url;
img.onload = () => console.log(`image url [${url}] has been loaded successfully`);
return img;
});
}
For anyone interested, here's some alternatives to code provided by OP.
preloadImage()
Function now returns
function preloadImage = function(url){
const img = new Image();
img.src = url;
return img
}
v1: Preload by passing images as arguments to preloadImages()
Returns array of Image typed objects returned by function. Useful to check status of preload.
jsFiddle
function preloadImage(url){
const img = new Image();
img.src = url;
return img
}
function preloadImages() {
const images = []
for (var i = 0; i < arguments.length; i++) {
images[i] = preloadImage(arguments[i])
}
return images
}
//-- usage --//
const images = preloadImages(
"http://domain.tld/gallery/image-001.jpg",
"http://domain.tld/gallery/image-002.jpg",
"http://domain.tld/gallery/image-003.jpg"
)
v2: Preload by passing images as an array to preloadImages()
Not type safe
Overwrites provided array with an Image type object. Returns array of Image typed objects returned by function. Useful to check status of preload.
jsFiddle
function preloadImage(url){
const img = new Image();
img.src = url;
return img
}
function preloadImages(images) {
for (var i = 0; i < images.length; i++) {
images[i] = preloadImage(images[i])
}
return images
}
//-- usage --//
let arr = [
"http://domain.tld/gallery/image-001.jpg",
"http://domain.tld/gallery/image-002.jpg",
"http://domain.tld/gallery/image-003.jpg"
]
const images = preloadImages(arr)
console.dir(images)
v3: Preload by passing either array(s) and/or argument(s to preloadImages()
Type safe. Returns array of Image typed objects returned by function. Useful to check status of preload.
jsFiddle
function preloadImage(url){
const img = new Image();
img.src = url;
return img
}
function preloadImages() {
const images = []
let c = 0
for (var i = 0; i < arguments.length; i++) {
if (Array.isArray(arguments[i])) {
for(var arr = 0; arr < arguments[i].length; arr++) {
if(typeof arguments[i][arr] == 'string') {
images[c] = preloadImage(arguments[i][arr])
c++
}
}
}
else if(typeof arguments[i] == 'string') {
images[c] = preloadImage(arguments[i])
c++
}
}
return images
}
//-- usage --//
var arr = [
"http://domain.tld/gallery/image-001.jpg",
"http://domain.tld/gallery/image-002.jpg"
]
const images = preloadImages(
arr,
"http://domain.tld/gallery/image-003.jpg",
"http://domain.tld/gallery/image-004.jpg",
[
"http://domain.tld/gallery/image-005.jpg",
"http://domain.tld/gallery/image-006.jpg"
]
)
console.dir(images)
Inspiration derived from: http://perishablepress.com/3-ways-preload-images-css-javascript-ajax/
Related
I have an issue that I don't understand and I have no idea how to fix it, where to look for the cause. When I'm debugin (chrome) every thing is working, but during normal use it dosen't. For me is some kind of Science-Fiction, it would be better for me if it's more Science than Fiction :)
for (var i = 0; i < filteredAddedFiles.length; i++) {
if ((/\.(png|jpeg|jpg|gif)$/i).test(filteredAddedFiles[i].name)) {
(function (file) {
var reader = new FileReader();
var blob = b64toBlob(file.base64.replace('data:image/jpeg;base64,', ''), file.type);
reader.addEventListener("load", function () {
console.log(this);
var image = new Image();
image.src = window.URL.createObjectURL(blob);
image.onload = function () {
preview.innerHTML += drawHtml(image, file)
};
//I tried:
//(function (b) {
// var image = new Image();
// image.addEventListener("load", function () {
// preview.innerHTML += drawHtml(this, file);
// //window.URL.revokeObjectURL(image.src);
// });
// image.src = window.URL.createObjectURL(b);
//})(blob);
});
reader.readAsDataURL(blob);
})(filteredAddedFiles[i]);
} else {
errors += filteredAddedFiles[i].name + " Unsupported Image extension\n";
}
}
here I attached a short movie that shows how its working
link to movie
not working - I mean - it looks like the all thing inside for dosen't executed
EDIT: 1
#Teemu - I turn on logs in chrome console and all console.log's appear
#Katie.Sun - now the above for - console.log(filteredAddedFiles.length); is 0 - but when I'm debuging code the same console.log(filteredAddedFiles.length); have values !
EDIT: 2
#Matti Price
filteredAddedFiles - is the result of custom logic of page, filtering,
validation etc.
Everything starts here:
addedFiles = added(files); // files - FileList from input this is a read only array of obj
function added(from) {
var out = [];
for (var i = 0; i < from.length; i++) {
(function (obj) {
var readerBase64 = new FileReader();
readerBase64.addEventListener("load", function () {
var fileBase64 = readerBase64.result;
var row = { name: obj.name, size: obj.size, type: obj.type, base64: fileBase64 }
out.push(row);
});
readerBase64.readAsDataURL(obj);
})(from[i]);
}
return out;
}
then addedFiles - do something farther and transform into filteredAddedFiles later. what I found in added function? during debug there is an length value witch is correct, but when I opened the __proto__: Array(0) I found length property = 0.
Should this length value be equal to the value from above length?
The second thing:
I have to admit that I don't have enough knowledge aboute addEventListener. Are there any queues here or some thread etc?
EDIT: 3
After last #Teemu comment I made some changes (I had to read a lot aboute promisses etc:)), but output is the same console.log("out resolve", out); shows a array of object, but console.log("out.length then", out.length); shows 0 and the small blue i-icon show msg - Value below evaluated just now
var out = [];
for (var i = 0; i < files.length; i++) {
fillArray(files[i], out);
}
console.log("out resolve", out);
console.log("out.length then", out.length);
function fillArray(obj, out) {
return new Promise(function (resolve, reject) {
var readerBase64 = new FileReader();
readerBase64.addEventListener("load", function () {
var fileBase64 = readerBase64.result;
var row = { name: obj.name, size: obj.size, type: obj.type, out.push(row);
resolve(out);
});
readerBase64.readAsDataURL(obj);
});
}
After I posted the edit above I relized that I just create promise, I forgot to call `promise
, but this is not important, because 90% of my code has been changed because of this topic and the answer of the Golden Person #Kaiido
URL.createObjectURL() - is synchronous. You don't need your Promise wrapping event handlers hell, all can be done in a single loop.
In my case, this is a better solution than a filereader, I have to upload only images, and with some restrictions thanks to which I don't have to worry about freeze the Internet browser, because of the synchronous nature of createObjectURL()
Thank you for your help and commitment
I'm using multiple image preloading with JavaScript, and I'm assigning some style attributes to each of them, which is possible since these are HTML Images elements. I use the Promise technique, that I found here on StackOverflow:
function preloadImages(srcs) {
function loadImage(imgToLoad) {
return new Promise(function(resolve, reject) {
var img = new Image();
img.onload = function() {
resolve(img);
};
img.onerror = img.onabort = function() {
reject(imgToLoad.src);
};
img.className = 'testimg'; //Test to see if I can retrieve img
img.src = imgToLoad.src;
img.style.top = imgToLoad.top;
});
}
var promises = [];
for (var i = 0; i < srcs.length; i++) {
promises.push(loadImage(srcs[i]));
}
return Promise.all(promises);
}
An example of the value of "srcs":
srcs[0].top = '15%';
srcs[0].src = 'img/myImage1.jpg';
srcs[1].top = '24%';
srcs[1].src = 'img/myImage2.jpg';
The preloading part works perfectly, and I'm able to get all of the preloaded images at the end of the process, seeing that the style attributes have been applied, as following:
preloadImages(imgToLoad).then(function(imgs) {
// all images are loaded now and in the array imgs
console.log(imgs); // This works
console.log(document.getElementsByClassName('testimg')); //This doesn't
}, function(errImg) {
// at least one image failed to load
console.log('error in loading images.');
});
However, later in my code, I would like to take some of the elements from the array of preloaded images, so I can append them without having to precise the style attributes.
By that, I mean something more than only .append('aSrcThatHasBeenPreloaded'); . I want to keep the class and style attributes I defined during the preload.
Is that even possible? And if so, how should I proceed?
I tried document.getElementsByClassName('myPreloadedClass') but it returns an empty result, which means the preloaded images are not part of the DOM (which makes perfect sense).
Thank you for your help.
You're going to have to make sure you can somehow retrieve the img elements you've preloaded. There are many ways to do this but three that come to mind straight away.
1. Added loaded image in the DOM
In your example you're trying to retrieve the images with a query selector. This fails because the img elements aren't added to the DOM. You could attach them to a DOM element that is hidden from the user.
var
images = [
{
src: '//placehold.it/550x250?text=A',
top: 0,
class:'img-a' // unique class name to retrieve item using a query selector.
},
{
src: '//placehold.it/550x250?text=B',
top: 0,
class:'img-b'
}],
imageCache = document.getElementById('image-cache');
function preloadImages(srcs) {
function loadImage(imgToLoad) {
return new Promise(function(resolve, reject) {
var img = new Image();
img.onload = function() {
// The image has been loaded, add it to the cache element.
imageCache.appendChild(img);
resolve(img);
};
img.onerror = img.onabort = function() {
reject(imgToLoad.src);
};
img.className = 'testimg ' + imgToLoad.class; //Test to see if I can retrieve img
img.src = imgToLoad.src;
img.style.top = imgToLoad.top;
});
}
var promises = [];
for (var i = 0; i < srcs.length; i++) {
promises.push(loadImage(srcs[i]));
}
return Promise.all(promises);
}
// Load all the images.
preloadImages(images)
.then(result => {
// Add img B from the cache to the proof div.
document.getElementById('proof').appendChild(imageCache.querySelector('.img-b'));
});
.hidden {
display: none;
}
<div id="image-cache" class="hidden"></div>
<div id="proof"></div>
2. Added loaded image to a document fragment
If you don't want to add the img to the DOM you could add them to a document fragment. This way a visitor of the website won't be able to easily access them and you can still use a query selector to retrieve the image.
var
images = [
{
src: '//placehold.it/550x250?text=A',
top: 0,
class:'img-a' // unique class name to retrieve item using a query selector.
},
{
src: '//placehold.it/550x250?text=B',
top: 0,
class:'img-b'
}],
// Create a document fragment to keep the images in.
imageCache = document.createDocumentFragment();;
function preloadImages(srcs) {
function loadImage(imgToLoad) {
return new Promise(function(resolve, reject) {
var img = new Image();
img.onload = function() {
// The image has been loaded, add it to the cache element.
imageCache.appendChild(img);
resolve(img);
};
img.onerror = img.onabort = function() {
reject(imgToLoad.src);
};
img.className = 'testimg ' + imgToLoad.class; //Test to see if I can retrieve img
img.src = imgToLoad.src;
img.style.top = imgToLoad.top;
});
}
var promises = [];
for (var i = 0; i < srcs.length; i++) {
promises.push(loadImage(srcs[i]));
}
return Promise.all(promises);
}
// Load all the images.
preloadImages(images)
.then(result => {
// Add img B from the cache to the proof div.
document.getElementById('proof').appendChild(imageCache.querySelector('.img-b'));
// And try to add img B from the cache to the proof2 div. The image will only be visible once in the site.
document.getElementById('proof2').appendChild(imageCache.querySelector('.img-b'));
});
.hidden {
display: none;
}
<div id="proof"></div>
<div id="proof2" style="margin-top:1em"></div>
The downside
This second solution is very similar to the first one. You can still use a query selector to access the images. However both solutions have the same downside, once you place an image from the cache anywhere else on the site you can no longer retrieve the image from the cache. This is because an element can be in the DOM only once and appending it to another element will remove it from its previous parent element.
3. A reusable bit of code
I would recommend creating either a closure or an ES6 class where you can put all the functionality for preloading the images and retrieving them.
The closure from the snippet below returns two methods:
preloadImages which takes an array of images to preload.
getImage which takes a valid CSS selector which is used to retrieve the needed img element from the document fragment. Before returning the element it will clone it so you can add the same preloaded image multiple times to the DOM.
Cloning an element will also clone its ID, something to be mindful of. I think it is not much of an issue here since the img is created in code so you have full control over it.
I used a document fragment to store the preloaded images because I think it is a more elegant solution than adding the images to the DOM. It makes it a lot more difficult to get the img element without using the getImage method and thus prevents that the preloaded img element is somehow removed from the image cache.
var
images = [
{
src: '//placehold.it/550x250?text=A',
top: 0,
class:'img-a' // unique class name to retrieve item using a query selector.
},
{
src: '//placehold.it/550x250?text=B',
top: 0,
class:'img-b'
}];
var imagesPreloader = (function() {
var imagesCache = document.createDocumentFragment();
function loadImage(imgToLoad) {
return new Promise(function(resolve, reject) {
var img = new Image();
img.onload = function() {
imagesCache.appendChild(img)
resolve();
};
img.onerror = img.onabort = function() {
reject(imgToLoad.src);
};
img.className = 'testimg ' + imgToLoad.class ; //Test to see if I can retrieve img
img.src = imgToLoad.src;
img.style.top = imgToLoad.top;
});
}
function preloadImages(srcs) {
var promises = [];
for (var i = 0; i < srcs.length; i++) {
promises.push(loadImage(srcs[i]));
}
return Promise.all(promises);
}
function getImage(imageSelector) {
const
// Find the image in the fragment
imgElement = imagesCache.querySelector(imageSelector);
// When the selector didn't yield an image, return null.
if (imgElement === null) {
return null;
}
// Clone the image element and return it.
const
resultElement = imgElement.cloneNode();
return resultElement;
}
return {
getImage,
preloadImages
};
})();
imagesPreloader.preloadImages(images)
.then(result => {
console.log('Image with class "img-a": ', imagesPreloader.getImage('.img-a'));
document.querySelector('#proof').appendChild(imagesPreloader.getImage('.img-b'));
document.querySelector('#proof2').appendChild(imagesPreloader.getImage('.img-b'));
});
<div id="proof"></div>
<div id="proof2" style="margin-top:1em"></div>
I'm trying to control the background if it available or not. I see onerror using everywhere, but not for me. I have bg folder and background1.jpg to background4.jpg background pictures. For first 4 there is no problem. But background5.jpg not available in that folder. Onerror doesn't work. How can i do about that problem? Any ideas?
<script>
var background = document.createElement("img");
var positive=1;
var x=0;
for(var i=0; i<6; i++)
{
background.src = "bg/background"+i+".jpg"
background.onerror = "finisher()"
background.onload = function(){alert("Worked!");}
function finisher()
{
positive = 0;
}
if(positive = 1)
{
alert("Vuhhuu! ->"+x);
x++;
}
else
{
alert("Image not loaded!");
}
}
</script>
There are a bunch of things wrong with your code. First off, you can use a for loop like this and expect it to try each image:
for (var i=0; i<6; i++) {
background.src = "bg/background"+i+".jpg"
background.onerror = "finisher()"
background.onload = function(){alert("Worked!");}
}
That just won't do what you're trying to do. That will rapidly set each successive .src value without letting any of them actually load (asynchronously) to see if they succeed.
Second off, don't use a string for .onerror. Assign the function reference directly such as background.onerror = finisher;
Thirdly, you MUST assign onerror and onload handlers BEFORE you assign .src. Some browsers (IE raise your hand) will load the image immediately if your image is in the browser cache and if your onload handler is NOT already installed, you will simply miss that event.
If you're really trying to try each image and know which ones work, you will have to completely redesign the algorithm. The simple way would be to create 6 images, load all of them and then when the last one finishes, you can see which ones had an error and which ones loaded properly.
If you're just trying to find the first one that succeeds, then you can pursue a different approach. Please describe what you're actually trying to accomplish.
To see how many of the images load successfully, you can do this:
function testImages(callback) {
var imgs = [], img;
var success = [];
var loadCnt = 0;
var errorCnt = 0;
var numImages = 6;
function checkDone() {
if (loadCnt + errorCnt >= numImages) {
callback(success, loadCnt, errorCnt);
}
}
for (var i = 0; i < 6 i++) {
img = new Image();
(function(index) {
img.onload = function() {
++loadCnt;
success[index] = true;
checkDone(this);
};
)(i);
img.onerror = function() {
++errorCnt;
checkDone();
};
img.src = "bg/background" + i + ".jpg";
imgs.push(img);
success.push(false);
}
}
testImages(function(successArray, numSuccess, numError) {
// successArray numSuccess and numError here will tell you
// which images and how many loaded
});
Note, this is an asynchronous process because images load in the background. You won't know how many images loaded or which images loaded until the callback is called sometime later when all the images have finished.
Try to use try-catch method.
try{
background.src = "bg/background"+i+".jpg";
background.onload = function(){alert("Worked!");}
}catch(e){
console.log("Error!");
finisher();
}
As already mentioned, you've got a couple of other problems, but this is a slightly more concise way of expressing the same, with a few errors tidied up. You're still requesting a bunch of different images, however.
var background = document.createElement("img");
var positive = 1;
var x = 0;
background.onerror = function() {
positive = 0;
alert("Image not loaded!");
}
background.onload = function() {
alert("Vuhhuu! -> " + x);
x++;
}
for (var i = 0; i < 6; i++) {
background.src = "bg/background" + i + ".jpg";
}
Explanation: You don't need to bind your onerror and onload handlers every time you loop. Doing it once will be just fine.
This is the code I use to preload images, I'm not sure if it's the best one.
My question is, how can I fire and event, for an example alert(); dialog after is has finished loading all the images?
var preload = ["a.gif", "b.gif", "c.gif"];
var images = [];
for (i = 0; i < preload.length; i++) {
images[i] = new Image();
images[i].src = preload[i];
}
You can use the new "$.Deferred" if you like:
var preload = ["a.gif", "b.gif", "c.gif"];
var promises = [];
for (var i = 0; i < preload.length; i++) {
(function(url, promise) {
var img = new Image();
img.onload = function() {
promise.resolve();
};
img.src = url;
})(preload[i], promises[i] = $.Deferred());
}
$.when.apply($, promises).done(function() {
alert("All images ready sir!");
});
Might be a little risky to leave the Image objects floating around, but if so that could be fixed easily by shifting the closure. edit in fact I'll change it myself because it's bugging me :-)
Since your tags include jQuery, here's what I would do in jQuery, with heavy inspiration from this related answer:
function preloadImages(images, callback) {
var count = images.length;
if(count === 0) {
callback();
}
var loaded = 0;
$.each(images, function(index, image) {
$('<img>').attr('src', image).on('load', function() { // the first argument could also be 'load error abort' if you wanted to *always* execute the callback
loaded++;
if (loaded === count) {
callback();
}
});
});
};
// use whatever callback you really want as the argument
preloadImages(["a.gif", "b.gif", "c.gif"], function() {
alert("DONE");
});
This relies on the callback to jQuery's load function.
I wouldn't use the related answer simply because I don't like mucking around with Javascript's built-in prototypes.
I've seen something used to correct behavior on Masonry nice jQuery plugin (a plugin used to make nice layout composition on pages). This plugin had problems with blocks containing images and should delay his work when the images are loaded.
First solution is to delay on the onLoad event instead of document.ready. But this event can be quite long to wait for. So they use jquery.imagesloaded.js which can detect that all images in a div are loaded; especially this very short and nice code can handle cached images which does not fire the load event sometimes.
Today I needed to preload images and execute some code only after all images are loaded, but without jQuery and using Ionic2/Typescript2. Here's my solution:
// Pure Javascript Version
function loadImages(arrImagesSrc) {
return new Promise(function (resolve, reject) {
var arrImages = [];
function _loadImage(src, arr) {
var img = new Image();
img.onload = function () { arr.push([src, img]); };
img.onerror = function () { arr.push([src, null]); };
img.src = src;
}
arrImagesSrc.forEach(function (src) {
_loadImage(src, arrImages);
});
var interval_id = setInterval(function () {
if (arrImages.length == arrImagesSrc.length) {
clearInterval(interval_id);
resolve(arrImages);
}
}, 100);
});
}
// Ionic2 version
private loadImages(arrImagesSrc: Array<string>): Promise<Array<any>> {
return new Promise((resolve, reject) => {
...
function _loadImage(src: string, arr: Array<any>) {
...
}
...
}
You can use like this. Problematic url returns 'null'.
loadImages(['https://cdn2.iconfinder.com/data/icons/nodejs-1/512/nodejs-512.png', 'http://foo_url'])
.then(function(arr) {
console.log('[???]', arr);
})
Check out: http://engineeredweb.com/blog/09/12/preloading-images-jquery-and-javascript. It uses jQuery. In the comments, your direct issue is addressed by someone and a solution was posted there. I think it'll fit your needs.
As an alternative you could use a CSS technique to pre-load the images as shown here:
http://perishablepress.com/press/2008/06/14/a-way-to-preload-images-without-javascript-that-is-so-much-better/
and then use the global onLoad event which is fired when everything has been loaded
//Here is a pre-jquery method, but jquery is bound to be simpler to write.
function loadAlbum(A, cb, err, limit){
// A is an array of image urls;
//cb is a callback function (optional);
//err is an error handler (optional);
// limit is an (optional) integer time limit in milliseconds
if(limit) limit= new Date().getTime()+limit;
var album= [], L= A.length, tem, url;
while(A.length){
tem= new Image;
url= A.shift();
tem.onload= function(){
album.push(this.src);
}
tem.onerror= function(){
if(typeof er== 'function') album.push(er(this.src));
else album.push('');
}
tem.src= url;
// attend to images in cache (may not fire an onload in some browsers):
if(tem.complete && tem.width> 0){
album.push(tem.src);
tem.onload= '';
}
}
// check the length of the loaded array of images at intervals:
if(typeof cb== 'function'){
window.tryAlbum= setInterval(function(){
if(limit && limit-new Date().getTime()<0) L= album.length;
if(L== album.length){
clearInterval(tryAlbum);
tryAlbum= null;
return cb(album);
}
},
100);
}
return album;
}
I'm searching for techniques that work for doing this all day now, and I finally found the solution to all my problems: https://github.com/htmlhero/jQuery.preload
It can even preload sequentially for you, in batches of any size (two at a time, for example), and fire a callback each time a batch completes.
jquery preloading multiple images serially can be done with a callback.
function preload_images(images_arr, f, id){
id = typeof id !== 'undefined' ? id : 0;
if (id == images_arr.length)
return;
$('<img>').attr('src', images_arr[id]).load(function(){
console.log(id);
f(images_arr[id], id);
preload_images(images_arr, f, id+1);
});
}
For example:
preload_images(["img1.jpg", "img2.jpg"], function(img_url, i){
// i is the index of the img in the original array
// can make some img.src = img_url
});
Based on answer from Ben Clayton above.
There can be case where images load fail all together and one must still be able to get callback.
Here is my revised answer.
One ca easily find out hom many images loaded with success and how many ended up in error. this in callback will have that info
preloadImages(_arrOfImages, function() {
var response = this;
var hasError = response.errored.length + response.aborted.length;
if (hasError > 0) {
console.log('failed ' + hasError);
} else {
console.log('all loaded');
}
});
//
JSFIDDLE: https://jsfiddle.net/bababalcksheep/ds85yfww/2/
var preloadImages = function(preload, callBack) {
var promises = [];
var response = {
'loaded': [],
'errored': [],
'aborted': []
};
for (var i = 0; i < preload.length; i++) {
(function(url, promise) {
var img = new Image();
img.onload = function() {
response.loaded.push(this.src);
promise.resolve();
};
// Use the following callback methods to debug
// in case of an unexpected behavior.
img.onerror = function() {
response.errored.push(this.src);
promise.resolve();
};
img.onabort = function() {
response.aborted.push(this.src);
promise.resolve();
};
//
img.src = url;
})(preload[i], promises[i] = $.Deferred());
}
$.when.apply($, promises).done(function() {
callBack.call(response);
});
};
Is the function I wrote below enough to preload images in most, if not all, browsers commonly used today?
function preloadImage(url)
{
var img=new Image();
img.src=url;
}
I have an array of image URLs that I loop over and call the preloadImage function for each URL.
Yes. This should work on all major browsers.
Try this I think this is better.
var images = [];
function preload() {
for (var i = 0; i < arguments.length; i++) {
images[i] = new Image();
images[i].src = preload.arguments[i];
}
}
//-- usage --//
preload(
"http://domain.tld/gallery/image-001.jpg",
"http://domain.tld/gallery/image-002.jpg",
"http://domain.tld/gallery/image-003.jpg"
)
Source: http://perishablepress.com/3-ways-preload-images-css-javascript-ajax/
You can move this code to index.html for preload images from any url
<link rel="preload" href="https://via.placeholder.com/160" as="image">
In my case it was useful to add a callback to your function for onload event:
function preloadImage(url, callback)
{
var img=new Image();
img.src=url;
img.onload = callback;
}
And then wrap it for case of an array of URLs to images to be preloaded with callback on all is done:
https://jsfiddle.net/4r0Luoy7/
function preloadImages(urls, allImagesLoadedCallback){
var loadedCounter = 0;
var toBeLoadedNumber = urls.length;
urls.forEach(function(url){
preloadImage(url, function(){
loadedCounter++;
console.log('Number of loaded images: ' + loadedCounter);
if(loadedCounter == toBeLoadedNumber){
allImagesLoadedCallback();
}
});
});
function preloadImage(url, anImageLoadedCallback){
var img = new Image();
img.onload = anImageLoadedCallback;
img.src = url;
}
}
// Let's call it:
preloadImages([
'//upload.wikimedia.org/wikipedia/commons/d/da/Internet2.jpg',
'//www.csee.umbc.edu/wp-content/uploads/2011/08/www.jpg'
], function(){
console.log('All images were loaded');
});
const preloadImage = src =>
new Promise((resolve, reject) => {
const image = new Image()
image.onload = resolve
image.onerror = reject
image.src = src
})
// Preload an image
await preloadImage('https://picsum.photos/100/100')
// Preload a bunch of images in parallel
await Promise.all(images.map(x => preloadImage(x.src)))
CSS2 Alternative: http://www.thecssninja.com/css/even-better-image-preloading-with-css2
body:after {
content: url(img01.jpg) url(img02.jpg) url(img03.jpg);
display: none;
}
CSS3 Alternative: https://perishablepress.com/preload-images-css3/
(H/T Linh Dam)
.preload-images {
display: none;
width: 0;
height: 0;
background: url(img01.jpg),
url(img02.jpg),
url(img03.jpg);
}
NOTE: Images in a container with display:none might not preload.
Perhaps visibility:hidden will work better but I have not tested this. Thanks Marco Del Valle for pointing this out
I recommend you use a try/catch to prevent some possible issues:
OOP:
var preloadImage = function (url) {
try {
var _img = new Image();
_img.src = url;
} catch (e) { }
}
Standard:
function preloadImage (url) {
try {
var _img = new Image();
_img.src = url;
} catch (e) { }
}
Also, while I love DOM, old stupid browsers may have problems with you using DOM, so avoid it altogether IMHO contrary to freedev's contribution. Image() has better support in old trash browsers.
Working solution as of 2020
Most answers on this post no longer work - (atleast on Firefox)
Here's my solution:
var cache = document.createElement("CACHE");
cache.style = "position:absolute;z-index:-1000;opacity:0;";
document.body.appendChild(cache);
function preloadImage(url) {
var img = new Image();
img.src = url;
img.style = "position:absolute";
cache.appendChild(img);
}
Usage:
preloadImage("example.com/yourimage.png");
Obviously <cache> is not a "defined" element, so you could use a <div> if you wanted to.
Use this in your CSS, instead of applying the style attribute:
cache {
position: absolute;
z-index: -1000;
opacity: 0;
}
cache image {
position: absolute;
}
If you have tested this, please leave a comment.
Notes:
Do NOT apply display: none; to cache - this will not load the
image.
Don't resize the image element, as this will also affect the
quality of the loaded image when you come to use it.
Setting position: absolute to the image is necessary, as the image elements will eventually make it's way outside of the viewport - causing them to not load, and affect performance.
UPDATE
While above solution works, here's a small update I made to structure it nicely:
(This also now accepts multiple images in one function)
var cache = document.createElement("CACHE");
document.body.appendChild(cache);
function preloadImage() {
for (var i=0; i<arguments.length; i++) {
var img = new Image();
img.src = arguments[i];
var parent = arguments[i].split("/")[1]; // Set to index of folder name
if ($(`cache #${parent}`).length == 0) {
var ele = document.createElement("DIV");
ele.id = parent;
cache.appendChild(ele);
}
$(`cache #${parent}`)[0].appendChild(img);
console.log(parent);
}
}
preloadImage(
"assets/office/58.png",
"assets/leftbutton/124.png",
"assets/leftbutton/125.png",
"assets/leftbutton/130.png",
"assets/leftbutton/122.png",
"assets/leftbutton/124.png"
);
Preview:
Notes:
Try not to keep too many images preloaded at the same time (this can cause major performance issues) - I got around this by hiding images, which I knew wasn't going to be visible during certain events. Then, of course, show them again when I needed it.
This approach is a little more elaborate. Here you store all preloaded images in a container, may be a div. And after you could show the images or move it within the DOM to the correct position.
function preloadImg(containerId, imgUrl, imageId) {
var i = document.createElement('img'); // or new Image()
i.id = imageId;
i.onload = function() {
var container = document.getElementById(containerId);
container.appendChild(this);
};
i.src = imgUrl;
}
Try it here, I have also added few comments
Solution for ECMAScript 2017 compliant browsers
Note: this will also work if you are using a transpiler like Babel.
'use strict';
function imageLoaded(src, alt = '') {
return new Promise(function(resolve) {
const image = document.createElement('img');
image.setAttribute('alt', alt);
image.setAttribute('src', src);
image.addEventListener('load', function() {
resolve(image);
});
});
}
async function runExample() {
console.log("Fetching my cat's image...");
const myCat = await imageLoaded('https://placekitten.com/500');
console.log("My cat's image is ready! Now is the time to load my dog's image...");
const myDog = await imageLoaded('https://placedog.net/500');
console.log('Whoa! This is now the time to enable my galery.');
document.body.appendChild(myCat);
document.body.appendChild(myDog);
}
runExample();
You could also have waited for all images to load.
async function runExample() {
const [myCat, myDog] = [
await imageLoaded('https://placekitten.com/500'),
await imageLoaded('https://placedog.net/500')
];
document.body.appendChild(myCat);
document.body.appendChild(myDog);
}
Or use Promise.all to load them in parallel.
async function runExample() {
const [myCat, myDog] = await Promise.all([
imageLoaded('https://placekitten.com/500'),
imageLoaded('https://placedog.net/500')
]);
document.body.appendChild(myCat);
document.body.appendChild(myDog);
}
More about Promises.
More about "Async" functions.
More about the destructuring assignment.
More about ECMAScript 2015.
More about ECMAScript 2017.
The HTML Living Standard now supports 'preload'
According to the W3C HTML spec you can now preload using JavaScript like so:
var link = document.createElement("link");
link.rel = "preload";
link.as = "image";
link.href = "https://example.com/image.png";
document.head.appendChild(link);
Here is my approach:
var preloadImages = function (srcs, imgs, callback) {
var img;
var remaining = srcs.length;
for (var i = 0; i < srcs.length; i++) {
img = new Image;
img.onload = function () {
--remaining;
if (remaining <= 0) {
callback();
}
};
img.src = srcs[i];
imgs.push(img);
}
};
Yes this will work, however browsers will limit(between 4-8) the actual calls and thus not cache/preload all desired images.
A better way to do this is to call onload before using the image like so:
function (imageUrls, index) {
var img = new Image();
img.onload = function () {
console.log('isCached: ' + isCached(imageUrls[index]));
*DoSomething..*
img.src = imageUrls[index]
}
function isCached(imgUrl) {
var img = new Image();
img.src = imgUrl;
return img.complete || (img .width + img .height) > 0;
}
I can confirm that the approach in the question is sufficient to trigger the images to be downloaded and cached (unless you have forbidden the browser from doing so via your response headers) in, at least:
Chrome 74
Safari 12
Firefox 66
Edge 17
To test this, I made a small webapp with several endpoints that each sleep for 10 seconds before serving a picture of a kitten. Then I added two webpages, one of which contained a <script> tag in which each of the kittens is preloaded using the preloadImage function from the question, and the other of which includes all the kittens on the page using <img> tags.
In all the browsers above, I found that if I visited the preloader page first, waited a while, and then went to the page with the <img> tags, my kittens rendered instantly. This demonstrates that the preloader successfully loaded the kittens into the cache in all browsers tested.
You can see or try out the application I used to test this at https://github.com/ExplodingCabbage/preloadImage-test.
Note in particular that this technique works in the browsers above even if the number of images being looped over exceeds the number of parallel requests that the browser is willing to make at a time, contrary to what Robin's answer suggests. The rate at which your images preload will of course be limited by how many parallel requests the browser is willing to send, but it will eventually request each image URL you call preloadImage() on.
The browser will work best using the link tag in the head.
export function preloadImages (imageSources: string[]): void {
imageSources
.forEach(i => {
const linkEl = document.createElement('link');
linkEl.setAttribute('rel', 'preload');
linkEl.setAttribute('href', i);
linkEl.setAttribute('as', 'image');
document.head.appendChild(linkEl);
});
}
This is what I did, using promises:
const listOfimages = [
{
title: "something",
img: "https://www.somewhere.com/assets/images/someimage.jpeg"
},
{
title: "something else",
img: "https://www.somewhere.com/assets/images/someotherimage.jpeg"
}
];
const preload = async () => {
await Promise.all(
listOfimages.map(
(a) =>
new Promise((res) => {
const preloadImage = new Image();
preloadImage.onload = res;
preloadImage.src = a.img;
})
)
);
}
This is the original answer but a with a more modern ES syntax:
let preloadedImages = [];
export function preloadImages(urls) {
preloadedImages = urls.map(url => {
let img = new Image();
img.src = url;
img.onload = () => console.log(`image url [${url}] has been loaded successfully`);
return img;
});
}
For anyone interested, here's some alternatives to code provided by OP.
preloadImage()
Function now returns
function preloadImage = function(url){
const img = new Image();
img.src = url;
return img
}
v1: Preload by passing images as arguments to preloadImages()
Returns array of Image typed objects returned by function. Useful to check status of preload.
jsFiddle
function preloadImage(url){
const img = new Image();
img.src = url;
return img
}
function preloadImages() {
const images = []
for (var i = 0; i < arguments.length; i++) {
images[i] = preloadImage(arguments[i])
}
return images
}
//-- usage --//
const images = preloadImages(
"http://domain.tld/gallery/image-001.jpg",
"http://domain.tld/gallery/image-002.jpg",
"http://domain.tld/gallery/image-003.jpg"
)
v2: Preload by passing images as an array to preloadImages()
Not type safe
Overwrites provided array with an Image type object. Returns array of Image typed objects returned by function. Useful to check status of preload.
jsFiddle
function preloadImage(url){
const img = new Image();
img.src = url;
return img
}
function preloadImages(images) {
for (var i = 0; i < images.length; i++) {
images[i] = preloadImage(images[i])
}
return images
}
//-- usage --//
let arr = [
"http://domain.tld/gallery/image-001.jpg",
"http://domain.tld/gallery/image-002.jpg",
"http://domain.tld/gallery/image-003.jpg"
]
const images = preloadImages(arr)
console.dir(images)
v3: Preload by passing either array(s) and/or argument(s to preloadImages()
Type safe. Returns array of Image typed objects returned by function. Useful to check status of preload.
jsFiddle
function preloadImage(url){
const img = new Image();
img.src = url;
return img
}
function preloadImages() {
const images = []
let c = 0
for (var i = 0; i < arguments.length; i++) {
if (Array.isArray(arguments[i])) {
for(var arr = 0; arr < arguments[i].length; arr++) {
if(typeof arguments[i][arr] == 'string') {
images[c] = preloadImage(arguments[i][arr])
c++
}
}
}
else if(typeof arguments[i] == 'string') {
images[c] = preloadImage(arguments[i])
c++
}
}
return images
}
//-- usage --//
var arr = [
"http://domain.tld/gallery/image-001.jpg",
"http://domain.tld/gallery/image-002.jpg"
]
const images = preloadImages(
arr,
"http://domain.tld/gallery/image-003.jpg",
"http://domain.tld/gallery/image-004.jpg",
[
"http://domain.tld/gallery/image-005.jpg",
"http://domain.tld/gallery/image-006.jpg"
]
)
console.dir(images)
Inspiration derived from: http://perishablepress.com/3-ways-preload-images-css-javascript-ajax/