skip to Main Content

So I am doing some optimisation work on my web site to improve Page Insights scores, two points that I can address are:-

  • Serve images in next-gen formats
  • Defer offscreen images

So images in next-gen formats, I have decided to use WebP but need to include fallbacks so they work in all browsers/devices.

Defer offscreen images; I am using data-src with a bit of JS script to set the background image as the data-src, the JS replaces the initial SRC which is src="data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="

Taking the below as an example, how would I go about using inline background-image with WebP with fallbacks whilst deferring offscreen images?

HTML

<div id="working-bg" class="parallax" data-src="/wp-content/uploads/2016/08/silva-planning-parralax.jpg" style="background-image: url(/wp-content/uploads/2016/08/silva-planning-parralax.jpg)"></div>

JS for data-src

<script>
function init() {
    var imgDefer = document.getElementsByTagName('img');
    for (var i=0; i<imgDefer.length; i++) {
    if(imgDefer[i].getAttribute('data-src')) {
        imgDefer[i].setAttribute('src',imgDefer[i].getAttribute('data-src'));
    } }

    var imgDeferSpan = document.querySelectorAll('span[data-src]');
    var styleSpan = "background-image: url({url})";
    for (var i = 0; i < imgDeferSpan.length; i++) {
        imgDeferSpan[i].setAttribute('style', styleSpan.replace("{url}", imgDeferSpan[i].getAttribute('data-src')));
    }

    var imgDeferDiv = document.querySelectorAll('div[data-src]');
    var styleDiv = "background-image: url({url})";
    for (var i = 0; i < imgDeferDiv.length; i++) {
        imgDeferDiv[i].setAttribute('style', styleDiv.replace("{url}", imgDeferDiv[i].getAttribute('data-src')));
    }

}

window.onload = init;

</script>

Thanks in advance!

4

Answers


  1. Serve images in next-gen format:
    Use an image CDN like imagekit.io and get it done for you automatically. Most image CDNs have a free plan that’s great for testing and small sites.

    Defer offscreen images:
    Use a ready rolled solution like lozad.js that takes care of both images and background-images. For the background-image you can skip setting an initial inline style as the element has it’s size set and will not cause any reflow when the image loads. For <img>s use an empty <svg> as the initial src value where 6 9 in the viewBox below defines the relation between width (6) and height (9) => change those values to fit your context.

    data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 6 9'%3E%3C/svg%3E
    

    Note that lozad.js uses Intersection Observer API that still lacks in browser support so be sure to add a polyfill.

    Good luck!

    Login or Signup to reply.
  2. Edit: Answer was incorrect, removed it

    Checking on the backend for webp support where you serve the images should be the correct solution, and just redirect to the webp version if supported. Or do the same in JS, or another hacky solution can be, if you have nothing better is to load the img tag with the webp version, and onerror (<img onerror='loadJpg(this)/>) load the original version as a fallback, and make #bg.style.backgroundImage = img.src, it should update when the img.src changes

    Login or Signup to reply.
  3. Serve images in next-gen format:

    Convert *.jpeg or *.png images into *.jpeg.webp or *.png.webp and configure server to conditionally deliver webp.

    <IfModule mod_mime.c>
        AddType image/webp .webp
    </IfModule>
    <ifModule mod_rewrite.c>
        RewriteEngine On
        RewriteBase /
        RewriteCond %{HTTP_ACCEPT} image/webp
        RewriteCond %{REQUEST_URI} (?i)(.*).(jpe?g|png)$
        RewriteCond %{DOCUMENT_ROOT}%1.%2.webp -f
        RewriteRule (?i)(.*).(jpe?g|png)$ %1.%2.webp [T=image/webp,E=webp:1,L]
        <IfModule mod_headers.c>
            Header append Vary Accept env=REDIRECT_webp
        </IfModule>
    </IfModule>
    

    Defer offscreen images:

    Add loading=lazy attribute to <img> elements.

    Login or Signup to reply.
  4. Here is a my solution. It is a bit different to Tamás answer.
    With the optional class contain you can set the size of the image.
    The onload event secures the abillity of the final image.
    You load the image with a hidden picture element and fetch with js the loaded image (with fallback to jpg or png). Finally you set the loaded image with jquery as the background of the parent div.
    Maybe there is a better solution in the field but this works for me.

    function makeBgImage( img ){
        let srcImage;
        if ( typeof img.currentSrc === "undefined" ){
            //Old browser
            srcImage = img.src;
        }else{
            //Modern browser
            srcImage = img.currentSrc;
        }
        let ref = $(img).parents('div:first');
        ref.css('background', 'url(' + srcImage + ')');
        if( ref.hasClass('contain') ){
            ref.css('background-size', 'contain');
        }else{
            ref.css('background-size', 'cover');
        }
        ref.css('background-position', 'center');
        ref.css('background-repeat', 'no-repeat');
    }
    .hidden{display: none;}
    .img-container{
      width: 100%;
      padding-top: 62.5%; /* 8:5 Aspect Ratio */
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div class="img-container">
      <picture class="hidden">
        <source srcset="image.webp" type="image/webp">
        <img onload="makeBgImage(this)" alt="Image" src="image.png">
      </picture>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search