Lazy Load Intersection Observer

Utilisation de IntersectionObserver pour gérer le Lazy Load sur img picture iframe ou autres (à étendre si besoin).
Permet de charger les contenus dans le cas où le navigateur ne gère pas IntersectionObserver.
Ne pas oublier une petite balise noscript pour les navigateurs sans Javascript mais aussi pour le SEO.
Intersection Observer API sur MDN

Le script

Afficher / télécharger la source JS

;(async (__) => {

    'use strict';

    if(typeof DOM !== 'object') {

        const DOM = {
            document: __(document),
            body: __('body').first()
        };
    }


    let lazyClass = 'lazy-io',
        lazyClassLoaded = lazyClass + '-loaded',
        lazyAttributeData = 'srcset',
        lazyAttribute = 'data-' + lazyAttributeData,
        margins = {
            top: 0,
            right: 0,
            bottom: 100,
            left: 0
        };


    __.lazyIO = async function (fromDomNode) {

        if ("IntersectionObserver" in window) {

            const lazyLoad = async target => {

                let target_ = __(target),
                    tag = target_.prop('tagName').toLowerCase();


                // set attribute data-margins='{"bottom": 300, "left": 10, etc}' to specific margins instead of default 0 0 0 0
                if(target_.attr('data-margins') !== undefined) {

                    let json = __.parseJSON(target_.attr('data-margins'));

                    for(let i in json) {
                        margins[i] = json[i];

                    }
                }

                let options = {
                    rootMargin: margins.top + 'px ' + margins.right + 'px ' + margins.bottom + 'px ' + margins.left + 'px',
                    threshold: [0.0]
                };

                let lookAtIo = new IntersectionObserver((entries, observer) => {

                        entries.forEach(entry => {

                            if (entry.isIntersecting) {

                                let element_ = __(entry.target);

                                // backgrounds images
                                if (element_.hasClass(lazyClass + '-background')) {

                                    element_.css({
                                        backgroundImage: 'url(' + element_.data(lazyAttributeData) + ')'
                                    }).removeAttr(lazyAttribute).addClass(lazyClassLoaded);

                                } else {

                                    // load picture > source + img
                                    if (tag === 'picture') {

                                        element_.find('source').each(function () {
                                            let source_ = __(this);
                                            source_.attr(lazyAttributeData, source_.data(lazyAttributeData)).removeAttr(lazyAttribute);
                                        });

                                        // img must have a placeholder src eg. data:image/gif;base64,R0lGODlhCgAKAIABAAAAAP///yH5BAEAAAEALAAAAAAKAAoAAAIIjI+py+0PYysAOw==
                                        let img_ = element_.find('img').first();
                                        img_.attr('src', img_.data(lazyAttributeData)).on('load', () => {
                                            img_.removeAttr(lazyAttribute);
                                            element_.addClass(lazyClassLoaded);
                                        });

                                    }

                                    // load img
                                    if (__.inArray(tag, ['img', 'iframe']) !== -1) {

                                        // img must have a placeholder src eg. data:image/gif;base64,R0lGODlhCgAKAIABAAAAAP///yH5BAEAAAEALAAAAAAKAAoAAAIIjI+py+0PYysAOw==
                                        // iframe must have a fake src eg about:blank

                                        element_.attr('src', element_.data(lazyAttributeData)).on('load', () => {
                                            element_.addClass(lazyClassLoaded).removeAttr(lazyAttribute);
                                        })

                                    }

                                    // load xml Http Request
                                    if (__.inArray(tag, ['div', 'span', 'samp']) !== -1) {

                                        // no placeholder needed
                                        __.ajax({
                                            url: element_.data(lazyAttributeData),
                                            async: true,
                                            type: 'get',
                                            success: (response) => {
                                                if(element_.attr('data-type') !== undefined) {
                                                    if (element_.attr('data-type') === 'json') {

                                                        element_.text(JSON.stringify(response, null, '\t'));

                                                    }
                                                    // add other type if needed

                                                }  else {
                                                    element_.html(response);
                                                }

                                                element_.addClass(lazyClassLoaded).removeAttr(lazyAttribute);

                                                // on kortic.com (see JS sources or https://www.kortic.com/tooltip-infobulle-a11y-accessible.html)
                                                target_.find('[data-tooltip]').tooltip();

                                            },
                                            error: () => {
                                                /* do something */
                                            }
                                        });
                                    }
                                }



                                /* … optionally add other types of elements (asynchronous content, etc.) */

                                observer.disconnect();

                            }
                        });

                }, options);

                lookAtIo.observe(target);
            };

            let elements = __('.' + lazyClass, fromDomNode === null ? DOM.body : fromDomNode);

            elements.length && elements.each( function () {
                !__(this).hasClass(lazyClassLoaded) && lazyLoad(this);
            });

        } else {

            // fallback : charge tout si IntersectionObserver n'est pas pris en charge…
            __('.' + lazyClass, fromDomNode === null ? DOM.body : fromDomNode).each( async function() {

                let element_ = __(this),
                    tag = element_.prop('tagName').toLowerCase();

                // backgrounds images
                if (element_.hasClass(lazyClass + '-background')) {

                    element_.is(':visible')  && element_.css({
                        backgroundImage: 'url(' + element_.data(lazyAttributeData) + ')'
                    }).removeAttr(lazyAttribute).addClass(lazyClassLoaded);

                } else {
                    // load picture > source + img
                    if (tag === 'picture') {
                        let img_ = element_.find('img').first();

                        element_.addClass(lazyClassLoaded).find('source').each(function () {
                            let source_ = __(this);
                            source_.attr(lazyAttributeData, source_.data(lazyAttributeData)).removeAttr(lazyAttribute);
                        });

                        img_.attr('src', img_.data(lazyAttributeData)).removeAttr(lazyAttribute);
                    }

                    // load img
                    if (__.inArray(tag, ['img', 'iframe']) !== -1) {

                        element_.attr('src', element_.data(lazyAttributeData)).on('load', () => {
                            element_.addClass(lazyClassLoaded).removeAttr(lazyAttribute);
                        })

                    }

                    // load xml Http Request
                    if (__.inArray(tag, ['div', 'span', 'samp']) !== -1) {

                        __.ajax({
                            url: element_.data(lazyAttributeData),
                            async: true,
                            type: 'get',
                            success: (response) => {
                                switch (element_.attr('data-type')) {
                                    case 'json':
                                        element_.text(JSON.stringify(response, null, '\t'));
                                        break;

                                    // add other type if needed

                                    default:
                                        element_.html(response);
                                }


                                element_.addClass(lazyClassLoaded).removeAttr(lazyAttribute);

                                // on kortic.com (see JS sources or https://www.kortic.com/tooltip-infobulle-a11y-accessible.html)
                                target_.find('[data-tooltip]').tooltip();

                            },
                            error: () => {
                                /* do something */
                            }
                        });
                    }

                    /* … optionally add other types of elements (asynchronous content, etc.) */
                }
            });
        }

    };

    DOM.document.ready( function () {
        __('.' + lazyClass).length && __.lazyIO();
    });

})(jQuery);

La CSS

Afficher / télécharger la source CSS

/* nécessite un svg animé (https://www.kortic.com/svg/spritesheet.svg#i-loader par exemple) */

html.no-js .lazy-io {
    display: none;
}
/*
.lazy-io:not(.lazy-io-loaded):not(div):not(span) {
*/

.lazy-io {
    background-image: url(/svg/spritesheet.svg#i-wheel);
    background-size: 1.25rem 1.25rem;
    background-position: center center;
}

.lazy-io.lazy-io-loaded:not(.lazy-io-background) {
    background-image: none;
}

.lazy-io.lazy-io-loaded.lazy-io-background {
    background-size: cover;
}

picture.lazy-io:not(.lazy-io-loaded) {
    max-width: 100%;
    min-width: 100%;
    min-height: 100%;
}

Utilisation

Tout est dans le js... Exemple ici

Utilisation sur iframe Youtube
<iframe width="560" height="315" class="col xs-12 s-12 lazy-io" 
    src="about:blank" 
    data-srcset="https://www.youtube-nocookie.com/embed/stCxLxBMjYA" 
    allow="accelerometer; encrypted-media; gyroscope; picture-in-picture" allowfullscreen>
</iframe>
Utilisation sur une image
Vitae consequat mi ut
<img decoding="async"
    class="lazy-io"
    alt="Vitae consequat mi ut"
    src="/blank.gif"
    data-srcset="https://pictures.kortic.com/400/400/00b0b0.png"
    />
    <noscript>
        <img decoding="async"
            alt="Sed cras dolor"
            src="https://pictures.kortic.com/400/400/00b0b0.png"
            />        
    </noscript>
Utilisation sur picture
Iaculis massa orci
<picture class="lazy-io" data-margins='{"bottom":0}'>
    <source media="(max-width: 30rem)"  
            srcset="/blank.gif" 
            data-srcset="https://pictures.kortic.com/400/400/00b0b0.png" />
    <source media="(max-width: 63.9375rem)"  
            srcset="/blank.gif" 
            data-srcset="https://pictures.kortic.com/200/200/00b0b0.png" />
    <source media="(min-width: 64rem)"  
            srcset="/blank.gif" 
            data-srcset="https://pictures.kortic.com/250/250/00b0b0.png" />
    <img decoding="async" alt="Iaculis massa orci" 
         src="/blank.gif" 
         data-srcset="https://pictures.kortic.com/250/250/00b0b0.png" />
</picture>
<noscript>
    <img decoding="async" alt="Ligula lorem ligula mi sed" src="https://pictures.kortic.com/400/400/00b0b0.png" />
</noscript>
Utilisation en image de fond. Concernant le lazy load sur des images en background-image, il est nécessaire de disposer de plusieurs containers visibles selon le viewport.
Le rendu :
Le markup : <div class="ratio-66 lazy-io lazy-io-background query-display-xl" data-srcset="https://pictures.kortic.com/675/450/00b0b0.png"></div>
<div class="ratio-66 lazy-io lazy-io-background query-display-l" data-srcset="https://pictures.kortic.com/600/400/00b0b0.png"></div>
<div class="ratio-66 lazy-io lazy-io-background query-display-m" data-srcset="https://pictures.kortic.com/570/380/00b0b0.png"></div>
<div class="ratio-100 lazy-io lazy-io-background query-display-s" data-srcset="https://pictures.kortic.com/770/770/00b0b0.png"></div>
<div class="ratio-100 lazy-io lazy-io-background query-display-xs" data-srcset="https://pictures.kortic.com/400/400/00b0b0.png"></div>
Exemple de CSS : @media (min-width: 0) and (max-width: 29.9375rem) {
    [class*=query-display]:not([class*=query-display-xs]) {
        display: none;
    }
}

@media (min-width: 30rem) and (max-width: 47.9375rem) {
    [class*=query-display]:not([class*=query-display-s]) {
        display: none;
    }
}

@media (min-width: 48rem) and (max-width: 63.9375rem) {
    [class*=query-display]:not([class*=query-display-m]) {
        display: none;
    }
}

@media (min-width: 64rem) and (max-width: 81.1875rem) {
    [class*=query-display]:not([class*=query-display-l]) {
        display: none;
    }
}

@media (min-width: 81.25rem) {
    [class*=query-display]:not([class*=query-display-xl]) {
        display: none;
    }
}
Si vous souhaitez participer, même modestement, au maintien du site et pour son usage, vous pouvez cliquer sur le joli bouton…
Voir aussi…
  • Vérifier un numéro de carte bancaire Vérification de carte bancaire, script de validation de carte bancaire. Contrôle de format de numéro de carte bancaire, algorithme de Luhn carte de crédit Visa, Mastercard, Maestro, CB, Carte Bancaire, Discover Card, American Express, JCB Cards
  • Owl Carousel multiple Gérer plusieurs carrousels/sliders sur une seule page avec Owl Carousel 2. Manage multiple carousels/sliders on one page with Owl Carousel 2
  • Conversion SVG/CSS Encodage SVG pour CSS compatible Sass, SVG encoder, base64 SVG pour CSS. SVG to base64, Convert SVG to Data URI for css
  • Optimisation SVG Intégration SVG via xlink/use/symbol, factorisation de symbol SVG, optimisation SVG web performance
  • SVG Responsive Utilisation des media queries internes au SVG
  • Lazy Load Intersection Observer Lazy Load avec IntersectionObserver API pour images, picture, iframe, etc. Chargement lazy load de contenu, chargement différé de contenu de page web, lazy loading script
  • Design, flux et accessibilité jQuery Responsive layout logical DOM plugin
  • Lightbox Responsive et Accessible Lightbox images jQuery Responsive accessible a11y. Automatisation d'une Lightbox multipages. Lightbox image unique ou multiple. Lightbox responsive et accessible a11y wcag
  • Tooltip (infobulle) a11y Tooltip (infobulle) compatible a11y et SEO, naviguable au clavier, accessible par lecteur d'écran. Infobulle accessible, accessible tooltip
  • Zipcode patterns Contrôle automatisé de codes postaux internationaux, script zipcode, zipcode validator
  • Widget calendrier PHP Construction d'un widget « calendrier » en PHP et asynchrone
  • Responsive background-image jQuery responsive background-image plugin, plugin jQuery responsive background images
  • Drapeaux ISO 3366 SVG Drapeaux nationaux vectoriels normés ISO alpha 2, alpha 3 et numérotation Organisation des Nations Unies (ONU). Drapeaux SVG pour internationalisation de sites multilingues.
  • Layout rigolo Une autre approche pour définir un layout de page responsive.
  • Serveur d'images placeholders Serveur d'images placeholder pour le développement front-end
  • Générateur de noms API de génération de couples prénom/nom aléatoires.
  • Générateur images TTF API de génération de textes en images.
2020-10-22