'Background Image with WebP fallback and data-src

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!



Solution 1:[1]

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

Solution 2:[2]

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.

Solution 3:[3]

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>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 ddur
Solution 3 harley81