How do you cache an image in Javascript
Asked Answered
C

10

116

My friends and I are working on a website where we would like to cache certain images in order to display them faster in the future. I have two main questions:

  1. How do you cache an image?
  2. How do you use an image once it has been cached? (and just to verify, if an image is cached on page A, it is possible to call it from the cache to use it on page B, right?)

Also, is it possible to set when the cached version of the image will expire?

It would be much appreciated if an example and/or a link to a page which describes this further was included.

We're fine using either raw Javascript or the jQuery version.

Cetacean answered 20/4, 2012 at 4:7 Comment(4)
does the browser cache images automatically?Cetacean
@Logan: Yes, the browser caches images automatically, provided your server sends the necessary headers to tell the browser it's safe to cache it. (These headers may also tell the browser the expiration time, which is part of your question.)Synchroscope
how can I verify that my browser is sending the necessary headers?Cetacean
@LoganBesecker You can check Response Headers in the Network section of your browsers dev tools.Fordo
B
160

Once an image has been loaded in any way into the browser, it will be in the browser cache and will load much faster the next time it is used whether that use is in the current page or in any other page as long as the image is used before it expires from the browser cache.

So, to precache images, all you have to do is load them into the browser. If you want to precache a bunch of images, it's probably best to do it with javascript as it generally won't hold up the page load when done from javascript. You can do that like this:

function preloadImages(array) {
    if (!preloadImages.list) {
        preloadImages.list = [];
    }
    var list = preloadImages.list;
    for (var i = 0; i < array.length; i++) {
        var img = new Image();
        img.onload = function() {
            var index = list.indexOf(this);
            if (index !== -1) {
                // remove image from the array once it's loaded
                // for memory consumption reasons
                list.splice(index, 1);
            }
        }
        list.push(img);
        img.src = array[i];
    }
}

preloadImages(["url1.jpg", "url2.jpg", "url3.jpg"]);

This function can be called as many times as you want and each time, it will just add more images to the precache.

Once images have been preloaded like this via javascript, the browser will have them in its cache and you can just refer to the normal URLs in other places (in your web pages) and the browser will fetch that URL from its cache rather than over the network.

Eventually over time, the browser cache may fill up and toss the oldest things that haven't been used in awhile. So eventually, the images will get flushed out of the cache, but they should stay there for awhile (depending upon how large the cache is and how much other browsing is done). Everytime the images are actually preloaded again or used in a web page, it refreshes their position in the browser cache automatically so they are less likely to get flushed out of the cache.

The browser cache is cross-page so it works for any page loaded into the browser. So you can precache in one place in your site and the browser cache will then work for all the other pages on your site.


When precaching as above, the images are loaded asynchronously so they will not block the loading or display of your page. But, if your page has lots of images of its own, these precache images can compete for bandwidth or connections with the images that are displayed in your page. Normally, this isn't a noticeable issue, but on a slow connection, this precaching could slow down the loading of the main page. If it was OK for preload images to be loaded last, then you could use a version of the function that would wait to start the preloading until after all other page resources were already loaded.

function preloadImages(array, waitForOtherResources, timeout) {
    var loaded = false, list = preloadImages.list, imgs = array.slice(0), t = timeout || 15*1000, timer;
    if (!preloadImages.list) {
        preloadImages.list = [];
    }
    if (!waitForOtherResources || document.readyState === 'complete') {
        loadNow();
    } else {
        window.addEventListener("load", function() {
            clearTimeout(timer);
            loadNow();
        });
        // in case window.addEventListener doesn't get called (sometimes some resource gets stuck)
        // then preload the images anyway after some timeout time
        timer = setTimeout(loadNow, t);
    }

    function loadNow() {
        if (!loaded) {
            loaded = true;
            for (var i = 0; i < imgs.length; i++) {
                var img = new Image();
                img.onload = img.onerror = img.onabort = function() {
                    var index = list.indexOf(this);
                    if (index !== -1) {
                        // remove image from the array once it's loaded
                        // for memory consumption reasons
                        list.splice(index, 1);
                    }
                }
                list.push(img);
                img.src = imgs[i];
            }
        }
    }
}

preloadImages(["url1.jpg", "url2.jpg", "url3.jpg"], true);
preloadImages(["url99.jpg", "url98.jpg"], true);
Butterworth answered 20/4, 2012 at 4:30 Comment(8)
In this example you are creating a new Image object for each url. If you set the img variable outside of the loop and just update the src, it should have the same effect and cut down on resourcesIrregularity
@Irregularity - But to be certain that the browser would actually dowload and cache each image, you would have to wait until one image finished downloading before setting a new .src property. Otherwise the browser could just stop the prior download and never successfully cache it.Butterworth
It seems to work on newer versions of browsers without waiting, but you make a good point. I'll have to try it out on some older browsers to see it's compatibility :)Irregularity
@Irregularity - no need. I updated the code to remove the Image() element from the list when the image finishes loading. It is safer to wait until the image has finished loading.Butterworth
@Butterworth How does this not slow down the page unless this function is called asynchronously?Incommunicado
@zack - loading images this way is always asynchronous.Butterworth
I added another version of the script that waits until other document resources have been loaded so the preloading of images does not compete for bandwidth with the loading of the visible page resources.Butterworth
Why do we need to add the image to the array just to remove it? I mean couldn't we do the exact same thing without a list? creating new Image().src=imgs[i] without any variable to hold it? Or even if it is required just to remain with a local var img?Terminator
G
15

Adding for completeness of the answers: preloading with HTML

<link rel="preload" href="bg-image-wide.png" as="image">

Other preloading features exist, but none are quite as fit for purpose as <link rel="preload">:

  • <link rel="prefetch"> has been supported in browsers for a long time, but it is intended for prefetching resources that will be used in the next navigation/page load (e.g. when you go to the next page). This is fine, but isn't useful for the current page! In addition, browsers will give prefetch resources a lower priority than preload ones — the current page is more important than the next. See Link prefetching FAQ for more details.
  • <link rel="prerender"> renders a specified webpage in the background, speeding up its load if the user navigates to it. Because of the potential to waste users bandwidth, Chrome treats prerender as a NoState prefetch instead.
  • <link rel="subresource"> was supported in Chrome a while ago, and was intended to tackle the same issue as preload, but it had a problem: there was no way to work out a priority for the items (as didn't exist back then), so they all got fetched with fairly low priority.

There are a number of script-based resource loaders out there, but they don't have any power over the browser's fetch prioritization queue, and are subject to much the same performance problems.

Source: https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content

Grillo answered 13/9, 2019 at 19:15 Comment(0)
A
14

as @Pointy said you don't cache images with javascript, the browser does that. so this may be what you are asking for and may not be... but you can preload images using javascript. By putting all of the images you want to preload into an array and putting all of the images in that array into hidden img elements, you effectively preload (or cache) the images.

var images = [
'/path/to/image1.png',
'/path/to/image2.png'
];

$(images).each(function() {
var image = $('<img />').attr('src', this);
});
Aurel answered 20/4, 2012 at 4:13 Comment(2)
would it be possible to preload an image on one page(without displaying it), then display it on the next page?Cetacean
to my knowledge if you preload an image on one page it will be entered into the browsers cache and then will display quicker on any other page. if this is wrong, somebody please correct me.Aurel
S
7

Nowdays, there is a new technique suggested by google to cache and improve your image rendering process:

  1. Include the JavaScript Lazysizes file: lazysizes.js
  2. Add the file to the html file you want to use in: <script src="lazysizes.min.js" async></script>
  3. Add the lazyload class to your image: <img data-src="images/flower3.png" class="lazyload" alt="">
Sinclare answered 29/10, 2019 at 15:37 Comment(3)
This is good when we load the images in 2nd fold, even though we add this, google (pagespeed.web.dev) is suggesting to cache images, which is not possible from one of the e-commerce site (bigcommerce) do we have any work around pls. ?Kerrikerrie
@Kerrikerrie honestly I don't know, never worked with e-commerce websitesSinclare
No Problem.. Thank you :-)Kerrikerrie
C
4

There are a few things you can look at:

Pre-loading your images
Setting a cache time in an .htaccess file
File size of images and base64 encoding them.

Preloading: http://perishablepress.com/3-ways-preload-images-css-javascript-ajax/

Caching: http://www.askapache.com/htaccess/speed-up-sites-with-htaccess-caching.html

There are a couple different thoughts for base64 encoding, some say that the http requests bog down bandwidth, while others say that the "perceived" loading is better. I'll leave this up in the air.

Cookgeneral answered 20/4, 2012 at 4:13 Comment(1)
some example would be niceBenilda
C
4

Even though your question says "using javascript", you can use the prefetch attribute of a link tag to preload any asset. As of this writing (Aug 10, 2016) it isn't supported in Safari, but is pretty much everywhere else:

<link rel="prefetch" href="(url)">

More info on support here: http://caniuse.com/#search=prefetch

Note that IE 9,10 aren't listed in the caniuse matrix because Microsoft has discontinued support for them.

So if you were really stuck on using javascript, you could use jquery to dynamically add these elements to your page as well ;-)

Complexioned answered 10/8, 2016 at 12:39 Comment(1)
Nice nice nice nice!Sorority
D
2

I use a similar technique to lazyload images, but can't help but notice that Javascript doesn't access the browser cache on first loading.

My example:

I have a rotating banner on my homepage with 4 images the slider wait 2 seconds, than the javascript loads the next image, waits 2 seconds, etc.

These images have unique urls that change whenever I modify them, so they get caching headers that will cache in the browser for a year.

max-age: 31536000, public

Now when I open Chrome Devtools and make sure de 'Disable cache' option is not active and load the page for the first time (after clearing the cache) all images get fetch and have a 200 status. After a full cycle of all images in the banner the network requests stop and the cached images are used.

Now when I do a regular refresh or go to a subpage and click back, the images that are in the cache seems to be ignored. I would expect to see a grey message "from disk cache" in the Network tab of Chrome devtools. In instead I see the requests pass by every two seconds with a Green status circle instead of gray, I see data being transferred, so it I get the impression the cache is not accessed at all from javascript. It simply fetches the image each time the page gets loaded.

So each request to the homepage triggers 4 requests regardless of the caching policy of the image.

Considering the above together and the new http2 standard most webservers and browsers now support, I think it's better to stop using lazyloading since http2 will load all images nearly simultaneously.

If this is a bug in Chrome Devtools it really surprises my nobody noticed this yet. ;)

If this is true, using lazyloading only increases bandwith usage.

Please correct me if I'm wrong. :)

Demetra answered 7/3, 2017 at 17:4 Comment(0)
E
1

I have a similar answer for asynchronous preloading images via JS. Loading them dynamically is the same as loading them normally. they will cache.

as for caching, you can't control the browser but you can set it via server. if you need to load a really fresh resource on demand, you can use the cache buster technique to force load a fresh resource.

Edrisedrock answered 20/4, 2012 at 4:18 Comment(0)
F
0

Yes, the browser caches images for you, automatically.

You can, however, set an image cache to expire. Check out this Stack Overflow questions and answer:

Cache Expiration On Static Images

Fertilization answered 20/4, 2012 at 4:15 Comment(0)
E
0

I always prefer to use the example mentioned in Konva JS: Image Events to load images.

  1. You need to have a list of image URLs as object or array, for example:
var sources = {
    lion: '/assets/lion.png',
    monkey: '/assets/monkey.png'
};
  1. Define the Function definition, where it receives a list of image URLs and a callback function in its arguments list, so when it finishes loading the image, you can start execution on your web page:

function loadImages(sources, callback) {
    var images = {};
    var loadedImages = 0;
    var numImages = 0;
    for (var src in sources) {
        numImages++;
    }
    for (var src in sources) {
        images[src] = new Image();
        images[src].onload = function () {
            if (++loadedImages >= numImages) {
                callback(images);
            }
        };
        images[src].src = sources[src];
    }
}
  1. Lastly, you need to call the function. You can call it using
$(document).ready(function () {
    loadImages(sources, buildStage);
});
Endermic answered 28/8, 2017 at 7:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.