/* global jetpackLazyImagesL10n */ const jetpackLazyImagesModule = function () { const config = { // If the image gets within 200px in the Y axis, start the download. rootMargin: '200px 0px', threshold: 0.01, }; const loadingImages = []; let lazyImages, loadingWarning, observer; lazy_load_init(); const bodyEl = document.querySelector( 'body' ); if ( bodyEl ) { // Lazy load images that are brought in from Infinite Scroll bodyEl.addEventListener( 'is.post-load', lazy_load_init ); // Add event to provide optional compatibility for other code. bodyEl.addEventListener( 'jetpack-lazy-images-load', lazy_load_init ); } /** * Initialize the module. */ function lazy_load_init() { lazyImages = Array.from( document.querySelectorAll( 'img.jetpack-lazy-image:not(.jetpack-lazy-image--handled)' ) ); // If initialized, then disconnect the observer if ( observer ) { observer.disconnect(); } // If we don't have support for intersection observer, loads the images immediately if ( ! ( 'IntersectionObserver' in window ) ) { loadAllImages(); } else { // It is supported, load the images observer = new IntersectionObserver( onIntersection, config ); lazyImages.forEach( function ( image ) { if ( ! image.getAttribute( 'data-lazy-loaded' ) ) { observer.observe( image ); } } ); // Watch for attempts to print, and load all images. Most browsers // support beforeprint, Safari needs a media listener. Doesn't hurt // to double-fire if a browser supports both. window.addEventListener( 'beforeprint', onPrint ); if ( window.matchMedia ) { window.matchMedia( 'print' ).addListener( function ( mql ) { if ( mql.matches ) { onPrint(); } } ); } } } /** * Load all of the images immediately */ function loadAllImages() { if ( observer ) { observer.disconnect(); } while ( lazyImages.length > 0 ) { applyImage( lazyImages[ 0 ] ); } } /** * On intersection * * @param {Array} entries - List of elements being observed. */ function onIntersection( entries ) { // Loop through the entries for ( let i = 0; i < entries.length; i++ ) { const entry = entries[ i ]; // Are we in viewport? if ( entry.intersectionRatio > 0 ) { // Stop watching and load the image observer.unobserve( entry.target ); applyImage( entry.target ); } } // Disconnect if we've already loaded all of the images if ( lazyImages.length === 0 ) { observer.disconnect(); } } /** * On print */ function onPrint() { if ( ! loadingWarning && ( lazyImages.length > 0 || loadingImages.length > 0 ) ) { // Replace the printed page with a notice that images are still loading. // Hopefully the user won't actually print this, but if they do at least it'll not // waste too much ink. loadingWarning = document.createElement( 'div' ); loadingWarning.id = 'loadingWarning'; loadingWarning.style.fontWeight = 'bold'; loadingWarning.innerText = jetpackLazyImagesL10n.loading_warning; const s = document.createElement( 'style' ); s.innerHTML = '#loadingWarning { display: none; }\n@media print {\n#loadingWarning { display: block; }\nbody > #loadingWarning ~ * { display: none !important; }\n}'; loadingWarning.appendChild( s ); bodyEl.insertBefore( loadingWarning, bodyEl.firstChild ); } if ( lazyImages.length > 0 ) { loadAllImages(); } // May as well try an alert() too. The browser may block it, but if not // it could save them some trouble. if ( loadingWarning ) { alert( jetpackLazyImagesL10n.loading_warning ); } } /** * Apply the image * * @param {Element} image - The image object. */ function applyImage( image ) { let lazyLoadedImageEvent; if ( ! ( image instanceof HTMLImageElement ) ) { return; } const srcset = image.getAttribute( 'data-lazy-srcset' ); const sizes = image.getAttribute( 'data-lazy-sizes' ); // Remove lazy attributes. image.removeAttribute( 'data-lazy-srcset' ); image.removeAttribute( 'data-lazy-sizes' ); image.removeAttribute( 'data-lazy-src' ); // Add the attributes we want. image.classList.add( 'jetpack-lazy-image--handled' ); image.setAttribute( 'data-lazy-loaded', 1 ); if ( sizes ) { image.setAttribute( 'sizes', sizes ); } if ( ! srcset ) { image.removeAttribute( 'srcset' ); } else { image.setAttribute( 'srcset', srcset ); } // Force eager loading, otherwise the browser-native loading=lazy support will still // prevent the loading. image.setAttribute( 'loading', 'eager' ); loadingImages.push( image ); const idx = lazyImages.indexOf( image ); if ( idx >= 0 ) { lazyImages.splice( idx, 1 ); } if ( image.complete ) { loadedImage.call( image, null ); } else { image.addEventListener( 'load', loadedImage, { once: true } ); image.addEventListener( 'error', loadedImage, { once: true } ); } // Fire an event so that third-party code can perform actions after an image is loaded. try { lazyLoadedImageEvent = new Event( 'jetpack-lazy-loaded-image', { bubbles: true, cancelable: true, } ); } catch ( e ) { lazyLoadedImageEvent = document.createEvent( 'Event' ); lazyLoadedImageEvent.initEvent( 'jetpack-lazy-loaded-image', true, true ); } image.dispatchEvent( lazyLoadedImageEvent ); } /** * An image from applyImage() finished loading. */ function loadedImage() { const idx = loadingImages.indexOf( this ); if ( idx >= 0 ) { loadingImages.splice( idx, 1 ); } if ( loadingWarning && lazyImages.length === 0 && loadingImages.length === 0 ) { loadingWarning.parentNode.removeChild( loadingWarning ); loadingWarning = null; } } }; // Let's kick things off now if ( document.readyState === 'interactive' || document.readyState === 'complete' ) { jetpackLazyImagesModule(); } else { document.addEventListener( 'DOMContentLoaded', jetpackLazyImagesModule ); }