73

I have an anchor that changes its background image when hovered with a class class-btn that contains a background-image.

When hovered, it has

a.class-btn:hover
{
    background-image('path/to/image-hovered.jpg');
}

When the page loads the first time and you hover this button the first time, it blinks (it takes about half a second to download the hovered image). How to avoid that blinking without JavaScript (only simple css and html is allowed)?

I tried to search Stack Overflow for the similar question, but with no luck.

Just added:

  • Should I "preload" the hovered image? How?
  • Should I play with z-index or opacity?

It happens with all browsers and thus the solution should work for all browsers.

0

14 Answers 14

95

Here is a simple and effective css image preloading technique I have used several times. You can load several images by placing content: url() url() url()... etc.

body:after {
 display: none;
 content: url('path/to/image-hovered.jpg') url('path/to/another-image-hovered.jpg');
}
Sign up to request clarification or add additional context in comments.

10 Comments

This wasn't working for me at first, but I realize now that for some reason the element you apply these styles to MUST be body:after!
@Aaron Franke this works for me on any element:after
Looks like this does not work any more. My guess is that browsers optimize page loads in a way that the background for hidden elements is not loaded. Tried in recent Firefox, Safari and Chrome versions, none seem to load the image when display: none; is in the declaration. Same results when I try with width: 0; height: 0. If it is visible, it works, however adds unwanted image to the page. Anyone else facing the same?
@Grapestain same with me. Not working when there's display: none;
this solution seems to work in modern browsers stackoverflow.com/a/14390213/1108467
|
62

The easiest way to avoid this is to make use of image sprites. For a good overview, check out this CSS Tricks article.

That way, you not only solve the flicker problem you're seeing, but will also reduce the number of HTTP requests. Your CSS will look something like:

a.class-btn { background: url('path/to/image.jpg') 0 0 no-repeat; }
a.class-btn:hover { background-position: 0 -40px; }

The specifics will depend on your images. You can also make use of an online sprite generator to make the process easier.

3 Comments

What if I'm using background-size: contain? Any ideas?
Would this work if you wanted to use transition to fade between the images? I'm thinking not..
I wouldn't call this the easiest solution to fix flickering if the CSS is already otherwise ready. But it is a good way nonetheless.
15

A simple trick I use is to double up the original background image making sure to put the hovered image first

.next {
  background: url(../images/next-hover.png) center center no-repeat;
  background: url(../images/next.png) center center no-repeat;
    &:hover{
      background: url(../images/next-hover.png) center center no-repeat;
    }
 }

No performance hit and very simple

Or if you're not using SCSS yet:

.next {
  background: url(../images/next-hover.png) center center no-repeat;
  background: url(../images/next.png) center center no-repeat;        
 }
 .next:hover{
  background: url(../images/next-hover.png) center center no-repeat;
 }

2 Comments

if I was developing browser Id load only the valid background (so the last one) to minimize the network load. I think you can expect this is not gonna work in any browser sooner or later.
this worked OK until a recent chrome update, now it's optimized by the browser (which is great, but breaks the preload)
9

If you do this:

#the-button {
background-image: url('images/img.gif');
}
#the-button:before {
  content: url('images/animated-img.gif');
  width:0;
  height:0;
  visibility:hidden;
}

#the-button:hover {
  background-image: url('images/animated-img.gif');
}

This will really help!

See here:

http://particle-in-a-box.com/blog-post/pre-load-hover-images-css-only

P.S - not my work but a solution I found :)

3 Comments

Kinda hacky, but it works. I would add position: absolute; or much better display: none; instead of visibility: hidden; if you don't want to mess your layout.
Doesn't work if you actually want to use the pseudo element for something
@Srneczek display: none prevents image uploading, just add position: absolute
3

@Kristian's method of applying hidden 'content: url()' after the body didn't seem to work in Firefox 48.0 (OS X).

However, changing "display: none;" to something like:

body:after {
 position: absolute; overflow: hidden; left: -50000px;
 content: url(/path/to/picture-1.jpg) url(/path/to/picture-2.jpg);
}

... did the trick for me. Perhaps Firefox won't load hidden images, or maybe it's related to rendering(?).

1 Comment

Nice! I would suggest to use position:absolute; width:0; height:0; overflow:hidden; z-index:-1; though. Somehow left: -50000px freaks me out) Basically, the problem is the same as described here: stackoverflow.com/a/14390213/4810382
2

You can preload images

function preloadImages(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);
}
}
// then to call it, you would use this
var imageSrcs = ["src1", "src2", "src3", "src4"];
var images = [];
preloadImages(imageSrcs, images, myFunction);

Comments

2

This is a non-CSS solution: if the hover images are in one directory and have a common naming convention, for example contain a substring '-on.', it is possible to select the file names and put it into the HTML as a series of:

<img src='...' style='display: none' />

Comments

1

For me this worked a.class-btn{transition-delay: 0.1s;}

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
0

If they are the same dimensions, one possibility is to draw the two images directly on top of each other, with the CSS :hover class for the top image having display: none;

This way both images will be preloaded, but hovering will make the second visible.

Comments

0

The "double up the original background image" trick didn't work for me so I used another css trick:

.next {
    background: url(../images/next.png) center center no-repeat;        
}
.next:hover {
    background: url(../images/next-hover.png) center center no-repeat;
}
.next:after {
    content: url(../images/next-hover.png);
    display: none;
}

Comments

0

This technique works nicely for me and ensures not only is the hover image pre-loaded, but it's also ready and waiting to be displayed. (Most other solutions rely on switching the background image on hover, which just seems to take the browser a bit of time to figure out, however much the image is pre-loaded.)

Create :before and :after pseudo elements on the container with the two images, but hide the one you want to see on hover. Then, on hover, switch the visibility.

So long as they both share the same size and positioning rules, you should see a neat swap.

.image-container {
    &:before { display: block; background-image: url(uncovered.png); }
    &:after { display: none; background-image: url(uncovered.png); }
}
.image-container:hover {
    &:before { display: none; }
    &:after { display: block; }
}

Comments

0

I had the same issue. After trying everything related with css i can not solve the problem. What finally solved the problem was simulating that someone hovers the element. The css is the normal one.

CSS

#elemName{
 /* ... */
} 
#elemName:hover{
 /* change bg image */
}

JS

var element = document.getElementById('elemName');
var event = new MouseEvent('mouseover', {
  'view': window,
  'bubbles': true,
  'cancelable': true
});
element.dispatchEvent(event);

Comments

-2

Just change the size of the background image, instead of the source of it! So...

a.class-btn {
    background-image: url('path/to/image-hovered.jpg');
    background-size: 0;
}
a.class-btn:hover {
    background-size: auto;
}

1 Comment

OP wants a different image for default state and hover so this wouldn't work
-3

The best way to do this is to just insert the images onto the webpage and set display to none.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.