Stop Image Flicker With Cache-Control Extensions

Posted on August 05, 2004  |  

Posted in Development

71 comments

Image flicker in Internet Explorer/Win causes IE to get bitchslapped quite often. To get rid of this effect Dean Edwards suggests to configure Apache to keep images cached. Otherwise you need to manually change caching preferences in IE which a lot of people don’t know how to do. Besides, when images flicker, it’s you who looks bad, not the users’ IE. Dean says he’s no server expert, and neither am I, but there’s a way to configure IIS in a similar fashion.

Cache-Control Extensions is a little-known feature that was rolled out with Internet Explorer 5.0. In a nutshell, it’s a proprietary extension to the Cache-Control header IE 5.x and 6.x understand. The two extensions are pre-check and post-check.

Object request timing with cache-control extensions

Internet Explorer applies the following logic to objects served with these extensions:

  1. Upon first request, the object is cached and is served from cache until the post-check interval expires.
  2. Once the post-check interval expires IE fetches the object from cache and checks for an updated one in the background. If a newer object is available it caches it. Upon every subsequent request this updated (and now cached) object is served until the pre-check interval expires.
  3. Once the pre-check interval elapses the object is treated as expired. IE will first ask the HTTP server if the object has changed since it was requested by the browser. If it has, IE will load the updated object.

Note: I’m not referring to web pages. I refer to objects because we might be talking about images, web pages, style sheets, external JavaScript files, etc.

As MSDN states, the Refresh button will not trigger this logic because Refresh always sends the if-modified-since request to the server. Hyperlinks do trigger this logic.

How about an example? Suppose an HTTP server sends an image with the following header:

Cache-Control: post-check=3600,pre-check=43200

Both pre-check and post-check specify time intervals in seconds. We tell IE to cache the mentioned image for 12 hours (60 * 60 * 12 seconds). The first hour (60 * 60 seconds) IE will simply display the image from its local cache. However, after 60 minutes we want it to check for a newer one in the background, i.e. it will display the cached one and then do a background check. When 12 hours are up, IE checks for a modified image first.

Set Cache-Control Extensions In IIS

The only missing piece of the puzzle is where to set these extensions to get the ball rolling. You do it in the IIS Manager snap-in. You may choose to set them on a specific folder, such is a folder with images. You may also set them on your entire web app, but hardly ever would you want to cache every single page on your site.

Fire up the IIS Manager snap-in from Administrative Tools, pick a folder in your web app, right click, go to Properties, switch to the HTTP Headers tab, and click Add. Add cache-control extensions like this:

Add/edit custom HTTP header

Click Ok to dismiss the dialog. Your HTTP Headers tab should have extensions listed.

The Properties window

Set Cache-Control Extensions Programmatically

I was in for a big surprise when I found documentation for HttpCachePolicy.AppendCacheExtension. You can accomplish what we’ve talked about in C# like this:

Response.Cache.AppendCacheExtension(
        "post-check=900,pre-check=3600");

Now, if you want to serve images with this cache policy (which is a good idea) you need to assign them to the ASP.NET ISAPI extension in IIS because by default ASP.NET is not configured to pass them through its HTTP pipeline.

What I Don’t Know

What happens if either pre-check or post-check is missing? I don’t know. What happens if post-check is greater than pre-check? I don’t know either. I found no documentation on MSDN that talks about it.

71 comments

Roger
on August 5, 2004

Looks like something I'll take a look at when I get back to work (no IIS at home - when I have a choice I use Apache ;-)). I hope it works as well as the Apache solution you mentioned above. I have implemented it on my Inverted Sliding Doors tabs demo, and it seems to do the trick.


santogiacomo dominique
on August 8, 2004

no comment ok..


Brent Railey
on August 25, 2004

Thank you for this information, this got rid of my flickering problem I was also dealing with. I implemented the C# fix to a generated image.


Roger Johansson
on September 3, 2004

I can't figure out how to do this with Classic ASP, which is what most of our sites are on. Any hints?


Milan Negovan
on September 3, 2004

Those IIS settings are the same for ASP or ASP.NET applications. Or for any web applications hosted in IIS, for that matter.

I believe in classic ASP you can use Response.AddHeader to accomplish the same programmatically:

Response.AddHeader "Cache-Control", "post-check=3600,pre-check=43200"

Shoot me an email if you get stuck. ;)


Roger Johansson
on September 5, 2004

The IIS settings aren't always easily accessible if the site is hosted externally (which all of our clients' sites are), so being able to change it programatically is great. What you're suggesting looks like it could do the trick. Will report back when I have tested it.


Milan Negovan
on September 5, 2004

Aaaaah, your sites are hosted elsewhere. To cache images programmatically IIS needs to pass their processing to your code. Otherwise it serves them on its own.

In ASP.NET I would (1) write a custom HttpHandler, and (2) map images in IIS to the ASP.NET ISAPI. Normally, I call my hosting company and have them do this mapping because I don't have this much control over their IIS.

In classic ASP... Hmmm... There are really no HttpHandlers per se. Old-style ISAPI extensions and filters are written in C++, and I'd think a hosting company would throw a fit if you ask them to install yours.


Roger Johansson
on September 7, 2004

Just like you suspected, doing this programatically doesn't work in Classic ASP. Adding the header in IIS works, so I contacted our hosting company and had them add it where needed. Took them all of five minutes ;)
It's great to finally be flicker-free even when IE is configured the way many developers like it to be.


Andreas Wallberg
on November 27, 2004

Did anyone find a way to map these to only images, like
the ExpiresByType directive in HtAccess?

/Andreas


Jesse Hansen
on January 25, 2005

After implementing the fix above, I was still getting flickers in IE6 with Windows XP SP2 when the "Every time visit to the page" developer setting. I was able to eliminate the flicker by using the following header:

Cache-control: max-age=2592000;post-check=31449600,pre-check=31449600

You can adjust the numeric items accordingly.

I also had to check the Enable Content Expiration checkbox and set the expiration up to 30 days.


Milan Negovan
on January 25, 2005

Thank you for sharing this, Jesse!


Chris Neppes
on February 15, 2005

You can set IIS cache control manually, but for easy developer access to cache control with having to touch the Internet Services Manager, try CacheRight (http://www.cacheright.com).

You can also apply global changes more easily with this tool for IIS cache control -- it is all managed from one simple text rules file that lives in the Web root.

Cheers,
Chris @ Port80


George
on March 12, 2005

Just implemented this on my new websites. Not it flies !!!!!
Have been working as a web developer for ages, Know how to prevent caching but never thought that on images it's better to have cahing working. That thought never crossed my mind untill i saw this article.


George
on March 26, 2005

Question: Does this line "Cache-Control: post-check=3600,pre-check=43200" affect IE only or any standard browser ?
Basically is it part of the standard?


Milan Negovan
on March 27, 2005

George, as it says in this post it's "a proprietary extension to the Cache-Control header IE 5.x and 6.x understand". It's not a part of any standard.


George
on April 2, 2005

Milan, sorry i did not read carefully. I guess i was too excited. It really made a difference, especially in E-Commerce project when people often go back and forth between items and the catalog.
-----------------------------------
No the question is how do i make the same thing for FireFox for example? Will it work if i do something like that.
"Cache-Control:max-age=432000;post-check=36000,pre-check=432000" ?


Milan Negovan
on April 2, 2005

As a proprietary header to IE it shouldn't work for non-IE browsers (unless they choose to implement it).


Mike
on April 21, 2005

These two headers are completely unnecessary. Cache-Control: max-age= does just that, and much more. IE honors it very well. It's in HTTP/1.1 standard. It's honored in all proxy caches.

BTW someone mentioned earlier that they need "Cache-Control: max-age=2592000;post-check=31449600,pre-check=31449600" to get rid of the flicker. Guess what? you only need the max-age part.

In the book "HTTP Essential" the Cache-Control header is explained very clearly. I suspect Microsoft abandoned their header hack in IE6.


Johan
on April 24, 2005

Response.AddHeader "Cache-Control", "post-check=3600,pre-check=43200"

Is there an equivalent to do this for Apache php configuring the cahce settings?


Johan
on April 24, 2005

.htaccess
< FilesMatch "\.(gif|jpe?g|png)$" >
Header append Cache-Control "max-age=86400, must-revalidate"
< /FilesMatch >

would this do the job for Apache cache settings?


Simon
on April 24, 2005

I've added all the suggestions for header info, only the problem still persists. If you install ieHttpHeaders you will see IE6 still making image requests to the server.

This may be a bug in IE6. I don't have the same problem with firefox.


Travis Savo
on August 29, 2005

Seems that only masks the problem instead of hiding it.

First of all, with an expires/max-age of 'Way way way in the future', how do you deal with changing resources being cached forever on the client?

Secondly, if after caching out an image, does setting your clock ahead to after the cache expiration time cause the problem to reappear? It does for me... and I'm having a heck of a time fixing it.


Milan Negovan
on August 30, 2005

Travis, I definitely see images getting cached on the client forever (or until they clean out the cache). If it's not desired, and you expect them to change, set a reasonable timeout.

I haven't tried tweaking the system clock, but I'd imagine it can be an issue, as you point out.


Zareh
on September 28, 2005

I've set the IE setting to Check for a "Newer Page on Every Visit", and did some testing. I found the following:

Programmatic approach
I added the line of code Response.Cache.AppendCacheExtension("max-age=2592000; post-check=31449600, pre-check=31449600"); at the end of the Page_Load method.)
- Pages get cached (look at IE temp files folder, you will see a value in the expires column; web server log confirms page was not requested.)
- Images, stylesheets, etc. do NOT get cached (temp files folder entry has value "none" in the expires column; web-server log shows request for images/stylesheets.)

Setting IIS Custom Headers
I added the custom header "Cache-Control:max-age=3600" fr the virtual site.
- Pages get cached (as evidenced by value in temp files entry, and web server log)
- Images/stylesheets get cached (as evidenced by value in temp file entry expires colum - they now have an value!!! Also confirmed by web server log where there is NO request for these objects.)

The colclusion I take away is that the programmatic approach only affects the generated page, NOT the images/stylesheets/etc. the page may contain. The ONLY way to ask for caching of these objects appears to be to set a custom header value in IIS. Again, only the "max-age" value was needed 8-)


Milan Negovan
on September 29, 2005

Thank you, Zareh. Good to know this.


alkhan
on October 6, 2005

i'm amazed at the level of knowledge everyone on this post has. Unfortunately, I do not. This will sound like a "dumb" question but what is the flicker that everyone experiences? Our ASP.NET web application is strictly for data entry, running calculations and reports. It's a desktop data application transformed for the web. We have various toolbars and image files for buttons and logo. I don't seem to experience any flicker. The only annoying flicker is from the page post back when dynamically filtering/setting web controls or tree controls. You feel as though you are in an "epileptic" state when filling in data for a form. I need help with that without giving up the functionality of dynamic filtering/settings.

If someone has an example of the flickering that you are experiencing please pass it on. I'd like to see if I am experiencing the same thing without realizing ... as I said i don't have the level of knowledge as the people posting on this topic.

Al


Milan Negovan
on October 6, 2005

Al, it's not a stupid question at all. I can't think of a site that exhibits a flicker off the top of my head. Usually, you see it happen when a navigation menu has images as its backgound. You hover over the manu and IE takes a second or two to fetch the background image. This delayed change of state is the "flicker."


George
on October 27, 2005

Works great! No more annoying flickering when switching pages in IE. Please note that FireFox does a better job in eliminating flickering (caching) when switching pages.

Here's my setup in Apache 2 (.htaccess of vhost-conf):


ExpiresActive On
ExpiresByType text/html A1
ExpiresByType text/css "access plus 1 day"
ExpiresByType text/javascript "access plus 1 day"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/jpg "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"



Header append Cache-Control "post-check=3600, pre-check=43200"


Please note that the modules expires and headers must be enabled in Apache to function.

Regards,
George


George
on October 27, 2005

Uhm, some tags were filtered in my previous post. Again:


ExpiresActive On
ExpiresByType text/html A1
ExpiresByType text/css "access plus 1 day"
ExpiresByType text/javascript "access plus 1 day"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/jpg "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"


< IfModule mod_headers.c >
Header append Cache-Control "post-check=3600, pre-check=43200"


George
on October 27, 2005

Sorry, I give up. Please remember to enclose the directives with the appropiate module checks.


Simon
on November 2, 2005

Thanks for the Apache Fix - but asp.net runs on Windows/IIS.


Paul van Steenoven
on November 3, 2005

In case if you want to have flicker free IE mouseovers this code is for you:

a { mouseover.gif }
a:link, a:visited { regular.gif }
a:hover { mouseover.gif }

tadaah IE is flicker free :)


John Whistler
on November 4, 2005

Internet Information Services 5.0
Internet Explorer 6

I added the custom header "Cache-Control:max-age=3600" ,
but ONLY to the (virtual) directories which contain image files.
(ie., I did NOT add the header to any other content files -I want my asp application pages to never be cached).

The image flicker is gone.
Outstanding !!


Bob Everland
on December 13, 2005

Comment 33:
This does not work, this is the issue everyone is having is that styles are not cached by IE. If you follow the directions it will work. You can not programatically add the cache control using ColdFusion and classic ASP. I am curious if adding the cache control with ASP.NET makes it site wide, or only for your application.


Simone Busoli
on January 23, 2006

The only solution seems to be adding the Expires header to the response:

(C#)

Response.Expires = 10;

Where the number represents the minutes after which the response will expire.


Greg
on January 24, 2006

These solutions look great. But what if you (me in this case) are developing an application that is sold to companies around the world. Not every company uses the same web server to host the application. I would love to avoid having to do different fixes for this problem on different servers.


Simone Busoli
on January 24, 2006

You are absolutely right Greg, but actually the problem is much wider than this.
It's not only the servers that are different, but the clients too, which often is a much bigger problem.


Chris Scanlon
on January 25, 2006

I totally agree with what you're saying. I wish more people felt this way and took the time to express themselves. Keep up the great work.

Chris Scanlon
http://www.asphostingfun.com


Milan Negovan
on January 25, 2006

I hear you, Greg. I would like to avoid kludges, too. This is one of those cases where you can give yourself home field advantage if you have any degree of control over the server.


Sid
on February 7, 2006

As a note, if you want to make images stay cached forever using max-age=[big number], but you want the ability to update them, there is a solution... use a querystring. The image file "foo.jpg?1" works the same as, yet is different from, "foo.jpg?2". If you're going to use it all over the place, consider writing some helper JS code to cut down on maintenance.


Jason Holtzman
on February 16, 2006

I've heard several differnet solutions here so far, but I would like to know which one exactly works for IIS 5.0. I don't have access to our website's webserver as it is hosted remotely. I've already had them add the 'Cache-Control:max-age=3600' HTTP Header, but it doesn't seem to be working. After clearing out my Temp IE Files and reloading the home page, there still is no expiration date on the images I want cached. And the flicker is still there...


Jason Holtzman
on February 27, 2006

After further review, using the max-age header DID work for me. The only problem I still have is a slight delay with the image-shifting during rollover. Otherwise this worked like a charm.


praveen Ratnakar
on February 28, 2006

Hi!
I have set HTTP Header of IIS Folder(Video) Cache-Control:post-check=60,pre-check=120
I m saving different video file in this folder with same name(temp.wmv) But every time the same video file is played until i manually delete that file from Temporary internet Files.
Please please please Someone help me..........
I m really getting frustrated......


Ryan
on March 3, 2006

Had ISP set this header exactly as the article above said to for IIS (Cache-Control: post-check=3600,pre-check=43200) and it worked perfectly! - Thanks for info!


Kishan
on March 16, 2006

How to avoid cacheing of .js files in ASP.

Please help me I am struck very badly.

Thanks in advance.


Eric Lawrence
on April 4, 2006

Eric Lawrence from the IE Networking team here.

If you don't have both post-check and pre-check present, both directives are ignored.

If post-check > pre-check, both directives are ignored.


bughouse
on May 31, 2006

hint: mod_gzip


Jeffrey Schrab
on June 13, 2006

This saved our butts! Thanks a lot!


Julian
on June 22, 2006

Hi,

From the comments above is it better practice to use the:
Cache-Control:max-age=3600
or
Cache-Control: post-check=3600,pre-check=43200

Can you advise what happens when the 3600 or 43200 seconds expire? How do you get the images to still be cached and checked 'after the first hour'?

Similarly what do you recommend for the best practice when using the IIS HTTP Headers Enable Content Expiration. For example do you set directories to expire on a specific date say Fridays and then use that date to update the files, and then allocate another future expiration date?

If you use the Expire After... option what happens after that expiry timeframe - how do you get the content cached again?

Thanks in advance


Andrew Buck
on September 12, 2006

I spent ages trying to do this and finally found a great solution. I used a Perl script to serve the images (normal and hover images) with appropriate headers:

#!/usr/bin/perl
$|++;

my $file = '../WWW/tabhover.gif';
open(PIC, $file);

my(@MON) = qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/;
my(@WDAY) = qw/Sun Mon Tue Wed Thu Fri Sat/;
my($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime(time() + 24*3600);
$year += 1900;
$expires = sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT", $WDAY[$wday], $mday, $MON[$mon], $year, $hour, $min, $sec);

print "Expires: $expires\n";
print "Content-Type: image/gif\n\n";

binmode STDOUT;
print ;
close(PIC);
exit 0;

I did it like this as I didn't want my pages cached at all. Otherwise I could have maybe put these headers in the pages themselves.


David Levin
on September 12, 2006

Thanks so much for this article! I spent hours trying to fix this myself. You saved me at least 2 days of work!


Cezary Okupski
on September 17, 2006

Travis, if you know a change took place, refer to a resource with some version identifier, id est:
http://www.example.com/example.jpg?v=20050101
http://www.example.com/example.jpg?v=20060917


Mr. Pixel
on September 29, 2006

A js only solution: add this in the HEAD of the page:

try {
document.execCommand("BackgroundImageCache", false, true);
} catch(err) {}
Works for IE6 Service Pack1

Not needed for IE7.

Check my blog for details


tomek
on October 15, 2006

How to avoid cacheing of .js files in ASP.

Please help me I am struck very badly.

Thanks in advance.


kiran
on October 17, 2006

i had tried all these ie adding postcheck,precheck etc in IIS still flickering problem doesnt go,iam getting flickering problem only in pages where we have infragistics webgrid ..if anyone have suggesttions or solns please let me know.


Milan Negovan
on October 17, 2006

My guess is you'll need to ask Infragistics. :)


kiran
on October 18, 2006

yeah,i need to do that before asking them i want to know wether anyone had faced this problem before,thanks for sugestion though


praca
on October 20, 2006

I was reading your comments kiran and i have to say i got a similar problem;/ Did you find a solution for this? Plz let me now!


Plasma
on October 22, 2006

Really interesting, i must try this in one of my sites...


Killtek
on October 31, 2006

Mr. Pixel's javascript solution works.. Thank you!!!


Adam Rogas
on November 4, 2006

Using any caching at all works with


try
{
document.execCommand("BackgroundImageCache", false, true);
} catch(err) {}

Works for IE6 Service Pack1

as per Mr. Pixel

it also does not cause the images to be stuck in cache forever as a forced page reload will refresh the images.


Arzt
on November 12, 2006

I just want to say thanks, your solution works for me really good...


Master
on November 19, 2006

Thanks a lot for this article.


Plater
on November 28, 2006

As Andrew Buck did, I used a perl script to serve pages with custom headers. Now I've made the jump to ASP.NET to do the exact same thing.

Try something like the following:

Response.Clear();
Response.Cache.SetExpires(DateTime.Now.AddHours(12));
Response.ContentType="image/jpeg";
Response.WriteFile(filename);
Response.End();

The nice thing is you can use a query string or almost anything to decide what 'filename' is. I have taken to using tokens in the querystring to decide what file to return back to the browser.


Logodesign
on December 5, 2006

After further review, using the max-age header DID work for me. The only problem I still have is a slight delay with the image-shifting during rollover. Otherwise this worked like a charm.


Florian
on December 12, 2006

No more image flicker problems. Thank you so much!


Mattias
on December 14, 2006

@Plater:
Well done, now it works 100% without flickering...


Mag
on January 14, 2007

Are there any recent updates for the article?


Alex
on January 17, 2007

The Cache-Control HTTP Headers is part of the HTTP 1.1 standard.
It has a certain number of parameters that can be used:

* max-age=seconds - the number of seconds from the time of the request you wish this objcet to be keep into the cache;
* s-maxage=seconds - like max-age but it only applies to proxy;
* public - tell to handle the content has cacheable even if it would normally be uncacheable, it is used for example for authenticated pages;
* no-cache - force both proxy and browser to validate the document before to provide a cached copy;
* must-revalidate - tell the browser to obey to any information you give them about a webpage;
* proxy-revalidate - like must-revalidate but applies to proxy;


Guitar Teacher
on January 26, 2007

Oh God.

I only became aware of the flickering problem very late into the design stage.
The only solution I could come up was some ugly client-side hack.

Substitute the scriptless CSS menu by javascript if IE is detected -
Otherwise do nothing.

example in my guitar website

I wouldn't mind removing all the extra js junk tho
I like clean code.
but I have no .NET extension there or admin access to IIS
to force the change in cache policy.


Milan Negovan
on January 26, 2007

Check out this comment for a JavaScript-only solution.