Francisc
Francisc

Reputation: 80385

Preloading images with JavaScript

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.

Upvotes: 404

Views: 316643

Answers (22)

user40521
user40521

Reputation: 2119

Since I also needed visual feedback for the preloading process it was convenient to do this:

<img src="img.jpg" onload="className='foo'" class="bar">
<img src="img.jpg" onload="className='foo'" class="bar">
<img src="img.jpg" onload="className='foo'" class="bar">

then have the following css:

img{width:1px;height:1px}
.foo{border:5px solid green}
.bar{border:5px solid red}

The images then become red 11x11 cubes that turn green when loaded.

https://jsfiddle.net/gaby_de_wilde/brf6cod0/1/

It looks pretty nice for what little code is involved.

Upvotes: 0

Alexandre Daubricourt
Alexandre Daubricourt

Reputation: 4903

For React Native / Expo

To add up on other's answer, if you're using RN, you might want to use Image.prefetch(url).

Prefetches a remote image for later use by downloading it to the disk cache. Returns a promise which resolves to a boolean.

You can use it like so:

import { Image } from 'react-native';

await Image.prefetch('https://image.com');

Upvotes: 1

Rychu
Rychu

Reputation: 995

For me, the best and easiest way was to use CacheStorage. It is supported on all modern browsers on all platforms and is as simple as this:

async function preloadImage(src: string) {
    const cache = await caches.open('images');

    const isImageCached = await cache.match(src);

    if (isImageCached)
        return;

    const response = await fetch(src);

    await cache.put(src, response);
}

The only, and I believe for some critical, issue here is that it is a fetch call which means CORS rules apply here.

Upvotes: 1

Ulf Dellbr&#252;gge
Ulf Dellbr&#252;gge

Reputation: 1

Ok, I think this is useful to preload images using Promises.

function preloadImages(imageUrls) {
            const promises = [];
            const images = [];
            const number_of_urls = imageUrls.length

            for (let i = 0; i < number_of_urls; i++) {
                const img = new Image();
                images.push(img);
                promises.push(new Promise((resolve, reject) => {
                    img.onload = resolve;
                    img.onerror = reject;
                }));
                img.src = imageUrls[i];
            }
            return Promise.all(promises).then(() => images);
        }


        preloadImages(image_paths).then((images) => {
      
            const game_canvas = document.getElementById("winter-school");
            const render_window = game_canvas.getContext("2d");
            const test_image = images[0]
            render_window.drawImage(test_image, 0, height - (0 + test_image.naturalHeight))
          
        });

Upvotes: 0

david_adler
david_adler

Reputation: 10902

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)))

Upvotes: 49

TwistedOwl
TwistedOwl

Reputation: 1324

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);

Upvotes: 9

daanmlab
daanmlab

Reputation: 31

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;
                })
        )
    );
}

Upvotes: 3

Sandwich
Sandwich

Reputation: 2289

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/

Upvotes: 2

clintgh
clintgh

Reputation: 2077

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/

Upvotes: 65

Dexter Legaspi
Dexter Legaspi

Reputation: 3312

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;
    });
}

Upvotes: 0

Nanoo
Nanoo

Reputation: 903

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:

enter image description here

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.

Upvotes: 14

Brendan Farquharson
Brendan Farquharson

Reputation: 92

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);
    });
}

Upvotes: 3

Max Fomin
Max Fomin

Reputation: 820

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">

Upvotes: 75

Amin NAIRI
Amin NAIRI

Reputation: 2504

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.

Upvotes: 11

Mark Amery
Mark Amery

Reputation: 154545

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.

Upvotes: 3

Alexander
Alexander

Reputation: 7832

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');
});

Upvotes: 45

naeluh
naeluh

Reputation: 971

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);
    }
};

Upvotes: 5

Huzi--- Javiator
Huzi--- Javiator

Reputation: 3837

Yes. This should work on all major browsers.

Upvotes: 308

Robin
Robin

Reputation: 328

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;
}

Upvotes: 3

mplungjan
mplungjan

Reputation: 177685

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

Upvotes: 23

Dave
Dave

Reputation: 1961

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.

Upvotes: 10

freedev
freedev

Reputation: 30027

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

Upvotes: 12

Related Questions