Refresh image with a new one at the same url

Viewed 490452

I am accessing a link on my site that will provide a new image each time it is accessed.

The issue I am running into is that if I try to load the image in the background and then update the one on the page, the image doesn't change--though it is updated when I reload the page.

var newImage = new Image();
newImage.src = "http://localhost/image.jpg";

function updateImage()
{
if(newImage.complete) {
    document.getElementById("theText").src = newImage.src;
    newImage = new Image();
    number++;
    newImage.src = "http://localhost/image/id/image.jpg?time=" + new Date();
}

    setTimeout(updateImage, 1000);
}

Headers as FireFox sees them:

HTTP/1.x 200 OK
Cache-Control: no-cache, must-revalidate
Pragma: no-cache
Transfer-Encoding: chunked
Content-Type: image/jpeg
Expires: Fri, 30 Oct 1998 14:19:41 GMT
Server: Microsoft-HTTPAPI/1.0
Date: Thu, 02 Jul 2009 23:06:04 GMT

I need to force a refresh of just that image on the page. Any ideas?

23 Answers

Try adding a cachebreaker at the end of the url:

newImage.src = "http://localhost/image.jpg?" + new Date().getTime();

This will append the current timestamp automatically when you are creating the image, and it will make the browser look again for the image instead of retrieving the one in the cache.

As an alternative to...

newImage.src = "http://localhost/image.jpg?" + new Date().getTime();

...it seems that...

newImage.src = "http://localhost/image.jpg#" + new Date().getTime();

...is sufficient to fool the browser cache without bypassing any upstream caches, assuming you returned the correct Cache-Control headers. Although you can use...

Cache-Control: no-cache, must-revalidate

...you lose the benefits of the If-Modified-Since or If-None-Match headers, so something like...

Cache-Control: max-age=0, must-revalidate

...should prevent the browser from re-downloading the entire image if it hasn't actually changed. Tested and working on IE, Firefox, and Chrome. Annoyingly it fails on Safari unless you use...

Cache-Control: no-store

...although this still may be preferable to filling upstream caches with hundreds of identical images, particularly when they're running on your own server. ;-)

Update (2014-09-28): Nowadays it looks like Cache-Control: no-store is needed for Chrome as well.

2021 ANSWER: You can simply use fetch with the cache option set to 'reload' to update the cache:

fetch("my-image-url.jpg", {cache: 'reload', mode: 'no-cors'})

The following function will update the cache and reload your image everywhere in your page:

const reloadImg = url =>
  fetch(url, { cache: 'reload', mode: 'no-cors' })
  .then(() => document.body.querySelectorAll(`img[src='${url}']`)
              .forEach(img => img.src = url))

It returns a promise so you can use it like await reloadImg("my-image-url.jpg") if you wish.

Nowadays the fetch API is available almost everywhere (except on IE, of course).

After creating the new image, are you removing the old image from the DOM and replacing it with the new one?

You could be grabbing new images every updateImage call, but not adding them to the page.

There are a number of ways to do it. Something like this would work.

function updateImage()
{
    var image = document.getElementById("theText");
    if(image.complete) {
        var new_image = new Image();
        //set up the new image
        new_image.id = "theText";
        new_image.src = image.src;           
        // insert new image and remove old
        image.parentNode.insertBefore(new_image,image);
        image.parentNode.removeChild(image);
    }

    setTimeout(updateImage, 1000);
}

After getting that working, if there are still problems it is probably a caching issue like the other answers talk about.

<img src='someurl.com/someimage.ext' onload='imageRefresh(this, 1000);'>

Then below in some javascript

<script language='javascript'>
 function imageRefresh(img, timeout) {
    setTimeout(function() {
     var d = new Date;
     var http = img.src;
     if (http.indexOf("&d=") != -1) { http = http.split("&d=")[0]; } 

     img.src = http + '&d=' + d.getTime();
    }, timeout);
  }
</script>

And so what this does is, when the image loads, schedules it to be reloaded in 1 second. I'm using this on a page with home security cameras of varying type.

One answer is to hackishly add some get query parameter like has been suggested.

A better answer is to emit a couple of extra options in your HTTP header.

Pragma: no-cache
Expires: Fri, 30 Oct 1998 14:19:41 GMT
Cache-Control: no-cache, must-revalidate

By providing a date in the past, it won't be cached by the browser. Cache-Control was added in HTTP/1.1 and the must-revalidate tag indicates that proxies should never serve up an old image even under extenuating circumstances, and the Pragma: no-cache isn't really necessary for current modern browsers/caches but may help with some crufty broken old implementations.

This answer is based on several of the above answers but unifies and simplifies them a little and casts the answer as a JavaScript function.

function refreshCachedImage(img_id) {
    var img = document.getElementById(img_id);
    img.src = img.src; // trick browser into reload
};

I needed a solution to the problem of animated SVGs not restarting after they played through the first time.

This trick also works on other media like audio and video as well.

Try using a worthless querystring to make it a unique url:

function updateImage()
{
    if(newImage.complete) {
        document.getElementById("theText").src = newImage.src;
        newImage = new Image();
        number++;
        newImage.src = "http://localhost/image.jpg?" + new Date();
    }

    setTimeout(updateImage, 1000);
}

Heavily based on Doin's #4 code, the below example simplifies that code a great bit utilising document.write instead of src in the iframe to support CORS. Also only focuses on busting the browser cache, not reloading every image on the page.

Below is written in typescript and uses the angular $q promise library, just fyi, but should be easy enough to port to vanilla javascript. Method is meant to live inside a typescript class.

Returns a promise that will be resolved when the iframe has completed reloading. Not heavily tested, but works well for us.

    mmForceImgReload(src: string): ng.IPromise<void> {
        var deferred = $q.defer<void>();
        var iframe = window.document.createElement("iframe");

        var firstLoad = true;
        var loadCallback = (e) => {
            if (firstLoad) {
                firstLoad = false;
                iframe.contentWindow.location.reload(true);
            } else {
                if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
                deferred.resolve();
            }
        }
        iframe.style.display = "none";
        window.parent.document.body.appendChild(iframe);
        iframe.addEventListener("load", loadCallback, false);
        iframe.addEventListener("error", loadCallback, false);
        var doc = iframe.contentWindow.document;
        doc.open();
        doc.write('<html><head><title></title></head><body><img src="' + src + '"></body></html>');
        doc.close();
        return deferred.promise;
    }

I improved the script from AlexMA for showing my webcam on a web page wich periodically uploads a new image with the same name. I had issues that sometimes the image was flickering because of a broken image or not complete (up)loaded image. To prevent flickering I check the natural height of the image because the size of my webcam image did not change. Only if the loaded image height fits the original image height the full image will be shown on page.

  <h3>Webcam</h3>
  <p align="center">
    <img id="webcam" title="Webcam" onload="updateImage();" src="https://www.your-domain.com/webcam/current.jpg" alt="webcam image" width="900" border="0" />

    <script type="text/javascript" language="JavaScript">

    // off-screen image to preload next image
    var newImage = new Image();
    newImage.src = "https://www.your-domain.com/webcam/current.jpg";

    // remember the image height to prevent showing broken images
    var height = newImage.naturalHeight;

    function updateImage()
    {
        // for sure if the first image was a broken image
        if(newImage.naturalHeight > height)
        {
          height = newImage.naturalHeight;
        }

        // off-screen image loaded and the image was not broken
        if(newImage.complete && newImage.naturalHeight == height) 
        {
          // show the preloaded image on page
          document.getElementById("webcam").src = newImage.src;
        }

        // preload next image with cachebreaker
        newImage.src = "https://www.your-domain.com/webcam/current.jpg?time=" + new Date().getTime();

        // refresh image (set the refresh interval to half of webcam refresh, 
        // in my case the webcam refreshes every 5 seconds)
        setTimeout(updateImage, 2500);
    }

    </script>
</p>

Place a second copy of the image in the same spot, then remove the original image.

function refreshImg(ele){
    ele.insertAdjacentHTML('beforebegin',ele.outerHTML);
    ele.parentNode.removeChild(ele);
}

This will effectively refresh the image.

Crossbrowser too. insertAdjacentHTML, outerHTML, parentNode, and removeChild are all crossbrowser.

Performance wise, performance loss will most likely be negligible in most cases. @Paolo Bergantino's answer is probably better than this function. Only one DOM element is affected using his answer. Two elements with this function.

I had this same issue using the Unsplash random image feature. The idea of adding a dummy query string to the end of the URL is correct, but in this instance a completely random parameter doesn't work (I tried it). I can imagine it's the same for some other services too, but for unsplash the parameter needs to be sig, so your image URL would be, for example, http://example.net/image.jpg?sig=RANDOM where random is a random string that will NOT be the same when you update it. I used Math.random()*100 but date is suitable too.

You need to do the above because without it, the browser will see that the image at said path has already been loaded, and will use that cached image to speed up loading.

See https://github.com/unsplash/unsplash-source-js/issues/9

No need for new Date().getTime() shenanigans. You can trick the browser by having an invisible dummy image and using jQuery .load(), then creating a new image each time:

<img src="" id="dummy", style="display:none;" />  <!-- dummy img -->
<div id="pic"></div>

<script type="text/javascript">
  var url = whatever;
  // You can repeat the following as often as you like with the same url
  $("#dummy").load(url);
  var image = new Image();
  image.src = url;
  $("#pic").html("").append(image);
</script>

Simple solution: add this header to the response:

Cache-control: no-store

Why this works is clearly explained at this authoritative page: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control

It also explains why no-cache does not work.

Other answers do not work because:

Caching.delete is about a new cache that you may create for off-line work, see: https://web.dev/cache-api-quick-guide/

Fragments using a # in the URL do not work because the # tells the browser to not send a request to the server.

A cache-buster with a random part added to the url works, but will also fill the browser cache. In my app, I wanted to download a 5 MB picture every few seconds from a web cam. It will take just an hour or less to completely freeze your pc. I still don't know why the browser cache is not limited to a reasonable max, but this is definitely a disadvantage.

Related