const hasNativePicture = 'HTMLPictureElement' in window;
// Safari take time to update currentSrc property
function getCurrentSrc( $element, callback ) {
if ( $element.currentSrc ) {
callback( $element.currentSrc );
return;
}
setTimeout( () => {
getCurrentSrc( $element, callback );
}, 50 );
}
function getSrc( $element ) {
return new Promise( resolve => {
// Responsive image on browser that natively support it
if (
(
$element.hasAttribute( 'srcset' ) ||
$element.parentNode && $element.parentNode.nodeName === 'PICTURE'
) &&
hasNativePicture
) {
getCurrentSrc( $element, src => {
resolve( src );
} );
}
else {
resolve( $element.src || $element.getAttribute( 'xlink:href' ) );
}
});
}
function isDataURI( src ) {
return src.indexOf( 'data:' ) > -1;
}
/**
* Preload an image
*
* @param $element - DOM image to preload
* @param callback - Function called on image load (`{` $element, type: string `}`) => `{}`
* @param handleError - If true reject the promise on error
*
* @example
* ```ts
* onImageLoad( $image, callback, handleError ).then( () => {} )
*
* // To allow event cancelation, don't chain .then() directly after onImageLoad
* let preload = onImageLoad( $image, callback, handleError );
* preload.then( () => {} );
* ...
*
* preload.off();
* ```
*
* @returns Return a standard Promise + an .off() function to cancel event
*/
export function onImageLoad( $element: Element, callback?: ( data: FLib.Events.ImagesLoad.CallbackParam ) => void, handleError = false ): FLib.Events.ImagesLoad.PromiseLoad {
let _remove;
const PROM: FLib.Events.ImagesLoad.PromiseLoad = new Promise( function( resolve, reject ) {
let $img;
function onImageLoaded( e ) {
_remove();
$img = null;
if ( callback ) {
callback( {
$element,
"type": e.type
} );
}
if ( handleError && e.type === 'error' ) {
reject();
}
else {
resolve( {
$element,
"type": e.type
} );
}
}
_remove = function() {
if ( !$img ) {
return;
}
$img.removeEventListener( 'error', onImageLoaded );
$img.removeEventListener( 'load', onImageLoaded );
}
getSrc( $element ).then( src => {
// Image already loaded
if (
( hasNativePicture &&
src &&
!isDataURI(src) &&
($element as HTMLImageElement).complete) ||
( !hasNativePicture && ($element as HTMLImageElement).complete && !isDataURI( src ) )
) {
if ( callback ) {
callback({
$element,
"type": 'complete'
});
}
resolve({
$element,
"type": 'complete'
});
return;
}
if ( !src || isDataURI( src ) ) {
$img = $element;
}
else {
$img = document.createElement( 'IMG' );
$img.src = src;
}
$img.addEventListener( 'error', onImageLoaded );
$img.addEventListener( 'load', onImageLoaded );
} );
} ) as FLib.Events.ImagesLoad.PromiseLoad;
PROM.off = _remove;
return PROM;
}
/**
* Preload a list of images
*
* @param $elements - Array of images to preload
* @param callback - Function called on each image load (`{` $element, type:string `}`) => `{}`
* @param handleError - If true reject the promise on error
*
* @example
* ```ts
* onAllImagesLoad( $images, partialCallback, handleError ).then( () => {} )
*
* // To allow event cancelation, don't chain .then() directly after onAllImagesLoad
* let preload = onAllImagesLoad( $images, callback, handleError );
* preload.then( () => {} );
* ...
*
* preload.off();
* ```
*
* @returns Return a standard Promise + an .off() function to cancel event
*/
export function onAllImagesLoad( $images: NodeList | Element[], ...args: any[] ): FLib.Events.ImagesLoad.PromisesLoad {
const promArray: FLib.Events.ImagesLoad.PromiseLoad[] = [];
$images.forEach( $img => {
promArray.push( onImageLoad( $img, ...args ) );
} );
const promResult: FLib.Events.ImagesLoad.PromisesLoad = Promise.all( promArray ) as FLib.Events.ImagesLoad.PromisesLoad;
promResult.off = function() {
promArray.forEach( imageLoad => {
imageLoad.off();
} );
}
return promResult;
}