/* 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 );
}