CSS-only: Load images on demand

The brain is a muscle, and as all muscles, it needs regular exercise to keep sharp.

Thats why I decided to take very old (but efficient) web optimization technique and implement it in new crazy way. You most likely heard about loading images (and other resources) on demand – which is not only a common sense, but also a good way to speedup initial page load.  Usually it is implemented via JavaScript, but today I’ll share with you how you can achieve the same effect by using pure CSS.

The problem

Few more words about the problem: we have a set of images to show to a user, but only some of them will be visible to the person after initial page load. The rest of the images will be below the fold and user may never scroll down. So it make sense to load those images after user start scrolling the page down.

Prevent images to load

First problem we need to solve is how to prevent browser from loading all the images from start? I’ve tried several approaches here, but the one which worked for me is to have images as background images for some div elements and set its parent display: none. Almost all modern browsers are smart enough to do not load background images for hidden elements. When we decide its time to load/show the image we will set display: block to its parent.

When to start load images?

Next let’s think about what styles or CSS selectors are changes whiles user scrolls? I.e. how can we catch the moment when to start particular image request? My first approach was to use position: sticky, as it behaves like position: relative within its parent, until a given offset threshold is met in the viewport (in other words user scrolled to some position). After this point element start behave as position: fixed. Unfortunately, surrounding elements doesn’t note the change and hold their position and styling.

Then I’ve noted that user’s cursor hover different elements while scrolling the page. So, what if we put several page-wide div elements (with class .hoverer) on a page and start loading the image when user hover it? It may work, but there is no guarantee that user will hover all div elements on first page (so we can load images on a second page). This leads us to the next idea – what if we load all images for first two pages from very beginning. Then put div.hoverer on top of images on a second page. Once user will hover it – we will load images for the third page. Third page will also contain div.hoverer for fourth page and so on.

Images Lazy Load Diagramm

Next question is how can we determine what is the size of the page, so we can load first two screens of images initially? Unfortunately there is no way to do so, but we can make them big enough to fit majority of the screens (assuming all images are in equal, known in advance size). Examine HTML/CSS/Result tabs below in order to get a feeling of how it will work :

Keep image visible when cursor left

But here is a problem, even if we show/load image when user hover related div.hoverer, the image will disappear when user remove cursor from the div.hoverer. So how can we preserve hover state? My first idea was to have div.hoverer elements overlapping each other, so when you position cursor over one element, it will be positioned over all elements we’ve hovered already. But hover state is not propagated over the elements, i.e. only top element will receive :hover state. And even pointer-events: none won’t help us in this situation.

Okay, what if just delay appearance of display: none on image parent’s element? I’ve tried to use keyframes animation, but it didn’t work out for me. Fortunately for me, transition does exactly what I need. Instead of another paragraph of explanation – checkout this demo.

But guess what, here is another problem we can’t delay display: block state as it is not animatable property in CSS. And transitions works only with animatable properties. Another dead end? I don’t think so. Here is another trick we can do – let’s transition width of the element and let’s apply media query to the element. In media query we can specify relationship between element width and its visibility. Oh, but it is not allowed to set media query for particular HtmlElement. Media queries are applicable to viewport only… Then we have to put images into separate viewports, i.e. iframes. We can use iframe.srcdoc in order to avoid to have separate url for each image we might have.

Results and some comments

Here is our final result (make your viewport height equal to 5 pumpkins in order to get the best experience). The demo works in Webkit only because of iframe.srcdoc support, but you can easily make it work cross-browser if put html of iframes into separate files.

Wow, this is pretty long path to somewhat working solution. Would I even recommend to use it on production – no way. This is definitely not a best practice, but is an interesting task and some parts of it might be applicable in real-world web applications. I hope you had as much fun as I had, while working on this challenge. And if you have any ideas of how to improve my approach – please do not hesitate to contact me.

UPDATE:
Looks like there is a way to solve a problem with first two screens: we may have div element of viewport size (by using height: 100vh;). The element will contain set of images (wrapped into iframe as in my demo). The set should be big enough to fill frist two screens. And we will have a set of media queries to show different subsets of images depending on viewport height.

  • http://christian-fei.com/ Christian Fei

    Cool technique!
    Didn’t know that browsers are so smart nowadays ^^

    • http://podlipensky.com Pavel Podlipensky

      Yeah, most people underestimate power of CSS, which leads to security breaches from time to time…

  • Abigayle Frances

    I like it :)

  • isatrio

    Wow, “crazy” technique

  • http://www.claimid.com/koolinus kOoLiNuS

    that’s the spirit of “hacking” !!!

  • Pingback: Loading Images on Demand with Pure CSS | Flippin' Awesome

  • Carsten

    Nice work. But it’s limited to the web. :( On mobile you do not really have a hover state.

    • http://podlipensky.com Pavel Podlipensky

      Well, I didn’t have time to optimize it for mobile, but it is certainly doable: instead of :hover you can use :active pseudo class and put ontouchstart=”" into the body tag (don’t ask me why – this is how mobile browsers works nowadays). Effect will be the same.

      I had to mention another drawback – it won’t work when user will drag scrollbar manually (as element won’t be hovered by the cursor). On the other hand it seems to work with keyboard-based scrolling, because cursor have to stay inside the container.

      • Vince Falconi

        I assume by “keyboard scrolling,” you mean with the arrow keys (forgive me if I missed something)–the cursor does not have to stay inside the window for me to scroll with the keyboard.

        This is interesting, but I think it depends on too specific of conditions for it to work properly. I think that’s why this sort of thing is best left up to JS.

        • http://podlipensky.com Pavel Podlipensky

          Mmm, probably you’re right, but user had to place focus inside the container, which is usually done by cursor, so most likely it will stay there. And I completely agree with you – this is a job for JS definitely.

          On the other hand I think I can re-use some of those CSS tricks to implement something more production-ready. I’m looking into responsive web, but let me know if you have any other ideas where it could be useful.

    • http://devolute.net devolute

      This is an ingenious attempt, but with the proliferation of Windows 8 touchscreen devices and simliar, I’d imagine a lot of users would be left without any images.

  • Binyamin

    And what about mobile devices and when user have no :hover state?

    • http://paulirish.com Paul Irish

      pwned.

    • Chris

      … or users who use keyboard arrows to scroll? This approach really is pretty crazy. Kinda clever, but crazy. Too fragile for serious use. But thanks for sharing!

  • Tom Farmer

    The use of iframes is a deal breaker for me. Even if used on desktop only, what happens when the user decides to use pgdn or space to skip down pages?!
    Interesting experiment though =)

  • sjoerd

    In chrome in the second jsfiddle demo the “image” changes back to grey when je remove the cursor from the hoverer.