Image preloading techniques (for use as hover state CSS background images) don't seem to work on Chrome
Asked Answered
H

7

8

I'm using the following technique to pre-load images that are applied as CSS background images when hovering buttons:

#preload_area {
  background-image: 
    url(../images/image1.svg),
    url(../images/image2.svg);
  width: 0px;
  height: 0px;
  display: inline;
}

Also tried to pre-load just one image, this way:

#preload_area {
  background: url(../images/image1.svg) -9999px -9999px no-repeat;
}

None of this works: after hard refresh, when hovering my button the first time, I still see a blink (corresponding to loading the hover image). Obviously after that first time there's no blink any more.

Why is not working on Chrome? (it does work on Firefox)

Hanghangar answered 29/3, 2017 at 12:14 Comment(5)
There is no reason why the first code example wouldn't work. The second one should have just background instead of background-image – see developer.mozilla.org/en-US/docs/Web/CSS/…Helmholtz
Well, it doesn't. I'm using Chrome 57.0.2987.110 (64-bit) on Windows 10. Thanks for the comment, I rectified (still doesn't work on Chrome though). Works fine on Firefox.Hanghangar
@Hanghangar I think Chrome implements some kind of DOM virtualization, where only visible elements are rendered, so I'm not sure if you would be able to work around this issue, unless you put the image in the visible area while preloading, which of course, is undesired.Catherincatherina
Use a sprite instead and do the switch via background-position ...Servitude
The first snippet of code works for me on Chrome....maybe if you post your entire code we could know what is the problemRestorative
M
0

Live Demo: http://blackmiaool.com/soa/43093224/

No one promise that invisible images will be loaded. Browsers have right to not preload your invisible images, so the css approach in your question may not work in some browsers. The demo above is written by myself. It actually renders image on the screen to guarantee the image is loaded.

HTML:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>test</title>

</head>

<body>
    <h1 style="color:white;text-align:center;">Try to hover</h1>
    <div class="show-area"></div>
    <link rel="stylesheet" href="style.css">

    <script src="../js/jquery.min.js"></script>
    <script>
        function preloadImage(src, cb) {
            //create a image
            const $dom = $("<img />", {
                src: src
            });

            //check whether the image is already loaded
            if ($dom[0].naturalWidth) {
                cb && cb();
                return;
            }

            //Put the image at the left bottom of the screen, and set its opacity to 0.01 to keep it from people eyes. 
            //Since it's actually rendered on the screen, the browser must load the image
            $dom.css({
                opacity: 0.01,
                position: 'fixed',
                bottom: 0,
                left: 0,
                height: 1,
                width: 1,
                'z-index': 10000,
                'pointer-events': 'none',
            });

            $(document.body).append($dom);

            //listen its `load` event to remove it and invoke callback
            $dom.on("load", function() {
                $dom.remove();
                cb && cb();
            });
        }

        //try to get the urls in the css file, and preload them
        $("link").each(function() {
            const href = $(this).attr("href");
            $.get(href, function(style) {
                const urls = [];
                let match = style.match(/url\([\s\S]+?\)/g);

                match.forEach(function(str) {
                    str = str.replace(/\s/g, "")
                        .replace(/^url\(/, "")
                        .replace(/\)$/, "");
                    let url = str.match(/^["']?([\S]+?)["']?$/);
                    if (!url || !url[1]) {
                        console.warn("Can't find url of " + str);
                    } else {
                        url = url[1];
                        preloadImage(url);
                    }
                });
            });
        });
    </script>

</body>

</html>

css:

body{
    margin: 0;
    background-color: black;
}
.show-area {    
    height: 100px;  
    width: 300px;
    margin: auto;
    margin-top: 100px;
    background: url( ./1.jpg) no-repeat center;
    background-size: contain;
}

.show-area:hover {
    background-image: url("./2.jpg ");
}
Moderator answered 1/4, 2017 at 6:36 Comment(2)
@Hanghangar I updated the answer to add some comments for you.Moderator
@Hanghangar I updated the answer to make it support the snippet in your question.Moderator
L
0

Why is it not working on Chrome? Because all browser vendors want the fastest browser. They will not load unnessecary assets.

You want a cross browser way to preload? Use a sprite, as [CBroe] suggested. This solution has been around for ages and is rock solid. Any other trick, rendering the image invisible, can work today but be broken tomorrow.

Livengood answered 2/4, 2017 at 19:47 Comment(0)
M
0

Preloading in CSS doesn't actually mean that the file is loaded before everything else it just means it's the first resource to queue for download from your CSS file.

This means that your HTML has already been retrieved from the server and has probably already queued up or downloaded other resources before the CSS. It's not uncommon for CSS preloaded images to load after all of the HTML content.

Now while the image will be earlier in the queue than other resources referenced in the CSS it doesn't mean that it returns before those other resources. If the size of the file is larger than the other files being queued up it may take longer to be downloaded than those other files which are being downloaded at the same time.

One way to see what is happening with Chrome is to go to your webpage and navigate to the "Network" tab in the Chrome Devtools then refresh the page. It will show you the details of when each item is being loaded and how long that item takes to be received from the server.

Depending what image you're loading and your use case there are several other options.

1) If the file size is large and taking too long to download figure out how to reduce the file size.

2) If you have control of the page the user is navigating from you could prefetch the image for the cache in the prior page.

3) You could also try using HTML preload in addition to the CSS. HTML preloading I believe is only supported by Chrome at the moment so it might be perfect for this scenario. Add the following to the head of your html.

<link rel="preload" as="image" href="image1.svg">
<link rel="preload" as="image" href="image2.svg">
Magnitude answered 5/4, 2017 at 2:40 Comment(1)
Thanks. In DevTools no matter how long I wait, the hover image is loaded at the end of the list only when I actually hover my link. Preload simply doesn't work at all.Hanghangar
C
0

Made a test with chrome, it seams that the image is loaded. The blink is due to place the image i think. To beter understand take a look at this test.

A test with a very big image

div#preload_area::before {
 content: " ";
 background: url(http://gfsnt.no/oen/foto/Haegefjell_Jan_2013_Large.jpg) no-repeat;
}

div#preload_area {
  width: 50%;
  height:100vh;
}

div#preload_area:hover {
   background: url(http://gfsnt.no/oen/foto/Haegefjell_Jan_2013_Large.jpg);
   background-repeat:no-repeat;
   background-size:100% auto;
}
Commines answered 6/4, 2017 at 15:46 Comment(0)
S
0

IMHO, this is no preloading. It's just loading, and you use a trick to display the right image when you hover the button.

If you really want to preload, or, as I understand your need, "you want the image already there, when you try to hover the button", then you have different options:

  • prefetch:

<link rel="prefetch" href="image1.svg"> <link rel="prefetch" href="image2.svg">

A nice thing to add for this is that "there's no same-origin restriction for link prefetching".

  • preload:

<link rel="preload" href="image1.svg"> <link rel="preload" href="image2.svg">

With "preload", the resources must be downloaded, whereas it's not always the case with prefetch.

Preload is supported by Chrome, Opera, Android browser and some more, but no Firefox & others. More details here

These techniques are described in more depth on css-tricks.com

Hope this helps you.

Slander answered 7/4, 2017 at 9:45 Comment(0)
T
0

If you don´t want loading-gaps, you could use a sprite-image, or you can set the background image as base64 encoded image. In this case, the images are always loaded when the css file is loaded.

.myImg {
    background-image: url(data:image/svg+xml;base64,PD9...);
}

Here you can convert your svg images to base64: http://b64.io

Tamaratamarack answered 7/4, 2017 at 10:30 Comment(0)
E
0

I recently use this for "back to top" button in my blog http://anggit.com. Will it works for you?

CSS:

<style>
#to_top {
    display: block;
    width: 48px;
    height: 48px;
    overflow: hidden;
}
#to_top img { /* in case the actual image size is over 48px */
    width: 48px;
    height: 48px;
}
#to_top:hover #image-1 { /* hover will remove the 1st image, the 2nd image will appear */
    display: none;
}
</style>

HTML:

<a id="to_top" href="#">
    <img id="image-1" src="image48x48.png" alt="Top" />
    <img id="image-2" src="image48x48-hover.png" alt="Top" />
</a>
Exceptionable answered 4/2, 2018 at 2:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.