Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DRY lazy-loaded images with <noscript> fallback

I know that (a handful of) non-JavaScript users are out there and I'd like to cater for them instead of giving them poorer experience just because of their preference (be that for privacy reasons or whatever).

Most lazy-loading JS libraries seem to address this in the same fashion, for example see lazysizes:

<style>
    .no-js img.lazyload {
        display: none;
    }
</style>

<noscript>
    <img src="image.jpg" />
</noscript>
<img src="grey.jpg" data-src="image.jpg" class="lazyload" />

Mainly out of curiosity, I got to wondering if it would be possible to pull the fallback out of the <noscript> tag and add it to the DOM programmatically with JavaScript so that the image source didn't have to be duplicated in two image tags which would leave me with just:

<noscript>
    <img src="image.jpg" class="lazyload" width="600" height="400"/>
</noscript>

Here's what I've knocked together:

(function(attribute) {
    Array.prototype.forEach.call(document.getElementsByTagName("noscript"), function(node) {
        var parser = new DOMParser,
            el = parser.parseFromString(node.textContent, "text/xml").documentElement, // XML => <img/> required
            img = ("img" == el.tagName) ? el : el.getElementsByTagName("img")[0]; // allow for <img/> in <picture>

        img.setAttribute(attribute, img.getAttribute("src"));
        img.setAttribute("src", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC");

        node.insertAdjacentHTML("beforebegin", el.outerHTML);
    });
})("data-src"); // different libraries use different data attribute names

This appears to work everywhere (Chrome, Safari, Opera, Firefox) except Internet Explorer (naturally). I know that .textContent isn't available pre-IE9 but IE9+ all seem to be failing at the final hurdle - the .outerHTML. Am I doomed to failure and having to repeat myself in my markup?

Update: To clarify, I'd ideally like to be able to use arbitrary attributes (alt, title, etc.) in the image tag or even use responsive markup:

<noscript>
    <picture>
        <source ... />
        <source ... />
        <img src="image.jpg" />
    </picture>
</noscript>
like image 323
Nev Stokes Avatar asked Mar 19 '15 13:03

Nev Stokes


People also ask

What is a lazy loaded image?

Lazy Loading Images is a set of techniques in web and application development that defer the loading of images on a page to a later point in time - when those images are actually needed, instead of loading them up front.

Can you Lazyload background images?

IMG tags can leverage native browser lazy loading, which doesn't require any JavaScript. You can still lazy load background images if they're in HTML as an inline style.

What is lazy loading technique?

Lazy loading is the practice of delaying load or initialization of resources or objects until they're actually needed to improve performance and save system resources.

How do I know if Lazyload is working?

If you're not sure if lazy loading is working correctly, you can open the Chrome DevTools and check that the images are not loaded until the scroll. Here are the steps you should take: Open the Chrome DevTools, go to the Network tab and the Img filter. At this point, reload the page.


1 Answers

I'm the creator of lazySizes. This approach has multiple porblems:

  1. A noscript element is never renderend, which means it is not detectable, wether it is visible or not (or better said it is always invisible)
  2. You can't use statefull classes lazyloading and lazyload to give feedback to the user
  3. You can't pre-occupy the space for your lazy embed content (which is important for both a) user experience (no content jumping) and b) performance (no reflow)
  4. (It has problems in older browsers)
  5. The data-sizes="auto" feature can't be used

However if 4. and 5. isn't a problem for you, it is possible to use a noscript child element in conjunction with a lazyload parent to achieve this.

The markup could look something like this:

<div class="lazyload" data-noscript="">
    <noscript>
        <p>any kind of content you want to be unveiled</p>
    </noscript>
</div>

And the lazySizes plugin code would look something like this:

(function(){
    'use strict';

    var supportPicture = !!window.HTMLPictureElement;

    addEventListener('lazybeforeunveil', function(e){
        if(e.defaultPrevented || e.target.getAttribute('data-noscript') == null){return;}
        var imgs, i;
        var noScript = e.target.getElementsByTagName('noscript')[0] || {};
        var content = noScript.textContent || noScript.innerText || '';
        e.target.innerHTML = content;

        if(supportPicture){return;}

        imgs = e.target.querySelectorAll('img[srcset], picture > img');

        for(i = 0; i < imgs.length; i++){
            lazySizes.uP(imgs[i]);
        }
    });
})();

In case you like this, I might make an official plugin for this. Here is the plugin: https://github.com/aFarkas/lazysizes/tree/master/plugins/noscript

like image 139
alexander farkas Avatar answered Nov 08 '22 19:11

alexander farkas