384

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.

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
Francisc
  • 77,430
  • 63
  • 180
  • 276
  • 29
    Note that some (all?) browsers will release the image after some seconds if you haven't used it. To avoid this, keep a reference to the `img` object, e.g. in an array in the parent scope. – Tamlyn Oct 13 '14 at 13:14
  • 7
    What do you mean by "release the image"? If it was cached by the browser, it will stay there, right? – Francisc Oct 17 '14 at 19:07
  • 3
    It will stay cached on disk but not in RAM and sometimes disk is too slow (e.g. animating a sequence). – Tamlyn Oct 18 '14 at 08:40
  • 68
    A bit shorter: `(new Image()).src = url;` – e382df99a7950919789725ceeec126 Oct 23 '14 at 10:56
  • 1
    It looks like this approach do not working for my current Google Chrome 51 – Vitaly Zdanevich Jul 17 '16 at 13:57
  • Can you share a JSFiddle or something to see a demo of what you're saying? – Francisc Aug 30 '16 at 23:03
  • 1
    do you need to use that img obj later? or you can just match the url? – FluffyBeing Dec 19 '16 at 19:20
  • 39
    note this won't work when chrome devtool is open and 'disable chache' is enabled within the network panel – 牛さん Jan 24 '17 at 04:11
  • 19
    Even shorter: `new Image().src = url;` – Daniel X Moore Dec 05 '17 at 17:29
  • [Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image#Browser_compatibility) of `Image()` – alpakyol Mar 19 '19 at 13:48
  • 1
    @Tamlyn Note that Chrome (and perhaps other browsers) now have a distinct ["memory cache" and "disk cache"](https://stackoverflow.com/q/44596937/1709587) for loaded resources, rendering the optimisation you suggest unnecessary even in the case where you want to keep the image in memory. – Mark Amery May 06 '19 at 20:19
  • further note: if you open devtools network panel, 'disable cache' is enabled by *default*, and like @Wayou mentions, the browser will still reload images even if they were preloaded. – Josh Aug 08 '19 at 22:29
  • @DanielXMoore Yes it's shorter but looks really ugly. I prefer the parens around `new` calls so it's clear. – Ruan Mendes Sep 12 '19 at 01:53
  • @JuanMendes Personally I think it's a really simple readable code without the parentheses. The parentheses make it unnecessarily more complex. But to each their own. – Daan Oct 22 '19 at 09:23
  • Maybe it doesn't make a difference but as @Tamlyn said, this method caches the images on disk as opposed to the clintgh answer below caches them in memory. At least that is what the DevTools Network tab tells me when I reload the page with each method. – MilkyTech May 21 '20 at 16:27
  • 4
    This no longer works - you will need to append the image using: `document.body.appendChild(img)`. To keep it nice and tidy, I would recommend creating a new element for the purpose of storing images in. – Nanoo Jul 23 '20 at 17:20
  • How to use this image ones it is stored? When I do this above, Google Page Insights still tells me I should preload the image – Michael Feb 24 '23 at 12:26

19 Answers19

289

Yes. This should work on all major browsers.

Alex M.
  • 635
  • 1
  • 6
  • 19
Huzi--- Javiator
  • 3,631
  • 1
  • 17
  • 5
  • 130
    **Moderator Note:** Please stop flagging this as "not an answer". It is, in fact, a direct answer to the question that was asked. If you think the answer is wrong, or insufficiently supported with evidence, then *downvote* it. – Cody Gray - on strike Mar 20 '19 at 07:29
64

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/

Sandwich
  • 2,304
  • 3
  • 24
  • 30
clintgh
  • 2,039
  • 3
  • 28
  • 44
  • 3
    there's no `onload` handler for any of the images – Benny Schmidt Jul 27 '15 at 18:22
  • 24
    Could you explain why this is better? – JJJ Jan 29 '16 at 15:09
  • @Juhana > "Unobtrusive, gracefully degrading, and easy to implement, simply edit/add the image paths/names as needed — no other editing required. This method is especially convenient for preloading large numbers of images.." - Preloading with JavaScript Only, JavaScript Method #1, Jeff Starr, https://perishablepress.com/3-ways-preload-images-css-javascript-ajax/ – clintgh Feb 01 '16 at 03:57
  • @benny yep there is no onload here just run-of-code execution. It cans be easily wrapped in a function call (examples on linked site). Do like this great for just copy and pasting with PhpStorm. – BeNice Mar 31 '16 at 10:35
  • 3
    @BeNice I think you're misunderstanding, loading images is async, therefore you have to handle the `onload` state or you're just instantiating images in memory. – Benny Schmidt Mar 31 '16 at 15:35
  • 3
    If you use this solution, don't forget the var statement for the i-variable. Otherwise it will be set globally which can cause error that are really hard to solve (unless you know that you forgot the var statement. `for (var i = 0.....` – Mohammer Jun 13 '16 at 23:37
  • 2
    @Mohammer thanks for this, I just copied the code from the link I provided. I will edit the code now to add the `var` – clintgh Jun 14 '16 at 02:55
  • @JJJ I think maybe this method is better since the images are kept in memory cache whereas the OP method only has them in disk cache. – MilkyTech May 21 '20 at 16:21
  • This method is better, because you can select which image you want to use using the `image` array. – Nanoo Jun 06 '20 at 15:43
59

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">
Max Fomin
  • 652
  • 5
  • 6
  • 1
    This answer did not get enough attention as being one of the least cumbersome methods in practice. – Michael Flores Mar 31 '21 at 02:56
  • 1
    Agreed. This was very effective in preloading images within a carousel too. Thanks for posting! – Omar Sep 28 '21 at 22:30
  • Is there a way to do that in js like - mylinkwithpreload = document.createElement("link)...... myheader.appendChild(linkwithpreload) – Rooki Dec 17 '21 at 11:50
  • 3
    Well the OP asked, how to preload images *in JS* . this is not JS . – gabriel-kaam Jan 20 '22 at 14:08
  • 1
    Isn't this the only solution that is guaranteed to work? – Leo Jan 22 '22 at 02:38
  • This works rely nicely, even if this link is added inside the body (practical when this is generated by a Vuejs component) – tobiasBora Apr 08 '22 at 13:07
  • 2
    @KhomNazid Loading the image from this tag does **not** block rendering. You can see this by opening the network tab, setting a throttle, and viewing the waterfall diagram. The preloaded image starts, but the rest of the page is rendered while it is still downloading. – Benjamin Carlsson Jun 10 '22 at 15:58
43

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');
});
Mark Amery
  • 143,130
  • 81
  • 406
  • 459
Alexander
  • 7,484
  • 4
  • 51
  • 65
38
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)))
enyo
  • 16,269
  • 9
  • 56
  • 73
david_adler
  • 9,690
  • 6
  • 57
  • 97
23

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

mplungjan
  • 169,008
  • 28
  • 173
  • 236
  • Thanks mplungjan. Although it doesn't help me with this particular case, it is good to know. – Francisc Sep 06 '10 at 09:36
  • 5
    Am I right saying this will slow down loading of the page because these images need to be downloaded before the page can launch the load event? – jakubiszon Mar 07 '15 at 17:23
  • Possibly. Why not try? – mplungjan Mar 07 '15 at 21:34
  • 3
    I do not believe these will work on all browsers. I know that on Chrome images are not loaded until they are visible. Will need to remove the display: none; and instead try and position them so they cannot be seen. Can then hide with JS after everything has loaded if needed. – Bullyen Dec 29 '15 at 16:02
  • 3
    Background images in an element with `display: none` will not preload. – M - Mar 17 '16 at 21:44
  • 1
    This method worked for me only I (1) did not use `display: none`, (2) did not use the `width/height: 0`, (3) put `no-repeat` and a position such that the image would be outside (`-px -px` will get you there, so if your images are all 100px in height or less, you can use `0 -100px`, in other words `url(...) no-repeat 0 -100px`. – Alexis Wilke Aug 10 '16 at 00:37
  • After adding the preload tag with respective images. Same image call getting called again because it is written in html as img src, and after second call it get cached but preloading doesn't do this. Then concept of preloading fails here. Please suggest me something if i am wrong. – Hemant Nagarkoti Jun 24 '19 at 06:41
  • There is no img tag in my suggestion – mplungjan Jun 24 '19 at 07:10
14

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.
Nanoo
  • 836
  • 8
  • 16
  • Your simpler version solved my problem, thanks. I did have to move the appendChild call into the function body in order to make it work in the page head area, as it complained about document.body being null otherwise. – Stovey Apr 18 '22 at 19:59
12

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

freedev
  • 25,946
  • 8
  • 108
  • 125
11

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.

Dave
  • 1,823
  • 2
  • 16
  • 26
9

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.

Amin NAIRI
  • 2,292
  • 21
  • 20
9

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);
TwistedOwl
  • 1,195
  • 1
  • 17
  • 30
  • 2
    I get a `Resource was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate 'as' value and it is preloaded intentionally.` error when using this method :( Also the preload just doesn't work – xperator Apr 17 '22 at 09:46
5

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);
    }
};
naeluh
  • 951
  • 11
  • 17
3

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;
}
Robin
  • 318
  • 2
  • 11
  • 1
    Can you please link some reference about this browsers limit? – nulll Jul 10 '16 at 08:26
  • It is hard to find a reference (at least I never found one), but I used my own project to test this while looking closely at what was loaded from cache and server calls using Chrome's network tab in the Chrome developer tools (f12). I found the same behavior in Firefox and using mobile. – Robin Dec 06 '16 at 04:22
  • 2
    -1 because I carefully tested the claim in the first sentence here and found it to be false, at least in modern browsers. See stackoverflow.com/a/56012084/1709587. Yes, there's a limit on the max number of parallel HTTP requests the browser is willing to send, but it does eventually get round to requesting every URL you call preloadImage on, even if you do it the way shown in the question. – Mark Amery May 08 '19 at 10:57
3

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.

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
  • Does it matter whether the images are cached in memory or disk? it seems the OP method is cached on disk as opposed to @clintgh method which gets cached in memory. – MilkyTech May 21 '20 at 17:47
3

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

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

daanmlab
  • 21
  • 1
1

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/

Sandwich
  • 2,304
  • 3
  • 24
  • 30
0

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;
    });
}
Dexter Legaspi
  • 3,192
  • 1
  • 35
  • 26
0

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