htmlcssresponsive-design

Load a low-res background image first, then a high-res one


I am trying to optimize the size of my site when it is being outputted to the client. I am down to 1.9MB and 29KB when caching. The issue is that the first load contains an image which is very unoptimized for mobile devices; it has a 1080p resolution.

So I am looking for a method that allows me to first load a low-res version (min.bg.jpg) and once the site has loaded, use a high-res version - or even one with a resolution close to the device being used (NNNxNNN.bg.jpg or just bg.jpg).

The background is set using CSS just like everyone would expect. Its applied to the body and the entire statement looks like this:

body {
    background: url("/cdn/theme/images/bg.jpg");
    color: white;
    height: 100%;
    width: 100%;
    background-repeat: no-repeat;
    background-position: 50% 50%;
    background-attachment: fixed;
}

Now, I want to change that to use min.bg.jpg instead for the first load, and then something like this:

jQuery(function(){
    jQuery("body").[...]
});

Which way do I go on asynchronously downloading the new background, and then inserting it as the new CSS background image?

To show some differences, here is an example of the main and mini version I am using for testing:

Ingwie@Ingwies-Macbook-Pro.local ~/Work/BIRD3/cdn/theme/images $ file *.jpg
bg.jpg:     JPEG image data, EXIF standard
min.bg.jpg: JPEG image data, JFIF standard 1.01
Ingwie@Ingwies-Macbook-Pro.local ~/Work/BIRD3/cdn/theme/images $ du -h *.jpg
1,0M    bg.jpg
620K    min.bg.jpg

Solution

  • Here's the method I use...

    CSS:

    #div_whatever {
       position: whatever;
       background-repeat: no-repeat;
       background-position: whatever whatever; 
       background-image: url(dir/image.jpg);
       /* image.jpg is a low-resolution at 30% quality. */
    }
    
    #img_highQuality {
        display: none;
    }
    

    HTML:

    <img id="img_highQuality" src="dir/image.png">
    <!-- img.png is a full-resolution image. -->
    
    <div id="div_whatever"></div>
    

    JQUERY:

    $("#img_highQuality").off().on("load", function() {
        $("#div_whatever").css({
            "background-image" : "url(dir/image.png)"
        });
    });
    // Side note: I usually define CSS arrays because
    // I inevitably want to go back and add another 
    // property at some point.
    

    What happens:

    1. A low-res version of the background quickly loads.
    2. Meanwhile, the higher resolution version is loading as a hidden image.
    3. When the high-res image is loaded, jQuery swaps the div's low-res image with the high-res version.

    PURE JS VERSION

    This example would be efficient for changing one to many elements.

    CSS:

    .hidden {
       display: none;
    }
    
    #div_whatever {
       position: whatever;
       background-repeat: no-repeat;
       background-position: whatever whatever; 
       background-image: url(dir/image.jpg);
       /* image.jpg is a low-resolution at 30% quality. */
    }
    

    HTML:

    <div id="div_whatever"></div>
    <img id="img_whatever" class="hidden" src="dir/image.png" onload="upgradeImage(this);">
    

    JAVASCRIPT:

    function upgradeImage(object) {
        var id = object.id;
        var target = "div_" + id.substring(4);
    
        document.getElementById(target).style.backgroundImage = "url(" + object.src + ")";
    }
    

    UPDATE / ENHANCEMENT (1/31/2017)

    This enhancement is inspired by gdbj's excellent point that my solution results in the image path being specified in three locations. Although I didn't use gdbj's addClass() technique, the following jQuery code is modified to extract the image path (rather than it being hardwired into the jQuery code). More importantly, this version allows for multiple low-res to high-res image substitutions.

    CSS

    .img_highres {
      display: none;
    }
    
    #div_whatever1 {
      width: 100px;
      height: 100px;
      background-repeat: no-repeat;
      background-position: center center;
      background-image: url(PATH_TO_LOW_RES_PHOTO_1);
    }
    
    #div_whatever2 {
      width: 200px;
      height: 200px;
      background-repeat: no-repeat;
      background-position: center center;
      background-image: url(PATH_TO_LOW_RES_PHOTO_2);
    }
    

    HTML

    <div id="div_whatever1"></div>
    <img id="img_whatever1" class="img_highres" src="PATH_TO_HIGH_RES_PHOTO_1">
    
    <div id="div_whatever2"></div>
    <img id="img_whatever2" class="img_highres" src="PATH_TO_HIGH_RES_PHOTO_2">
    

    JQUERY

      $(function() {
          $(".img_highres").off().on("load", function() {
             var id = $(this).attr("id");
             var highres = $(this).attr("src").toString();
             var target = "#div_" + id.substring(4);
             $(target).css("background-image", "url(" + highres + ")");
          });
       });
    

    What's happens:

    1. Low res images are loaded for each of the divs based on their CSS background-image settings. (Note that the CSS also sets the div to the intended dimensions.)
    2. Meanwhile, the higher resolution photos are being loaded as hidden images (all sharing a class name of img_highres).
    3. A jQuery function is triggered each time an img_highres photo completes loading.
    4. The jQuery function reads the image src path, and changes the background image of the corresponding div. In the example above, the naming convention is "div_[name]" for the visible divs and "img_[same name]" for the high res images loaded in the background.