slider-pro-js
Version:
Modular JavaScript slider that is dependency-free, rich-featured, flexible and easy to use
213 lines (168 loc) • 9.96 kB
JavaScript
class LazyLoading {
// The namespace to be used when adding event listeners
namespace = 'lazyloading';
// Reference to the base slider instance
slider;
// Indicates whether
allowLazyLoadingCheck = true;
constructor( slider ) {
this.slider = slider;
this.init();
}
init() {
// The 'resize' event is fired after every update, so it's possible to use it for checking
// if the update made new slides become visible
//
// Also, resizing the slider might make new slides or thumbnails visible
this.slider.addEventListener( 'resize.' + this.namespace, this.resizeHandler.bind( this ) );
// Check visible images when a new slide is selected
this.slider.addEventListener( 'gotoSlide.' + this.namespace, this.checkAndLoadVisibleImages.bind( this ) );
// Check visible thumbnail images when the thumbnails are updated because new thumbnail
// might have been added or the settings might have been changed so that more thumbnail
// images become visible
//
// Also, check visible thumbnail images after the thumbnails have moved because new thumbnails might
// have become visible
if ( typeof this.slider.thumbnails !== 'undefined' ) {
this.slider.thumbnails.addEventListener( 'thumbnailsUpdate.' + this.namespace, this.checkAndLoadVisibleThumbnailImages.bind( this ) );
this.slider.thumbnails.addEventListener( 'thumbnailsMoveComplete.' + this.namespace, this.checkAndLoadVisibleThumbnailImages.bind( this ) );
}
}
resizeHandler() {
if ( this.allowLazyLoadingCheck === false ) {
return;
}
this.allowLazyLoadingCheck = false;
this.checkAndLoadVisibleImages();
if ( this.slider.sliderEl.getElementsByClassName( 'sp-thumbnail' ).length !== 0 ) {
this.checkAndLoadVisibleThumbnailImages();
}
// Use a timer to deffer the loading of images in order to prevent too many
// checking attempts
setTimeout(() => {
this.allowLazyLoadingCheck = true;
}, 500 );
}
// Check visible slides and load their images
checkAndLoadVisibleImages() {
if ( this.slider.sliderEl.querySelectorAll( '.sp-slide:not([ data-loaded ])' ).length === 0 ) {
return;
}
const sizeProperty = this.slider.settings.orientation === 'horizontal' ? 'width' : 'height';
const positionProperty = this.slider.settings.orientation === 'horizontal' ? 'left' : 'top';
const middleSlidePosition = parseInt( ( this.slider.slidesOrder.length - 1 ) / 2, 10 );
let slidesSize = 0,
averageSlideSize = 0;
if ( this.slider.settings.autoSlideSize === true ) {
const firstSlide = this.slider.slidesEl.getElementsByClassName( 'sp-slide' )[ this.slider.slidesOrder[ 0 ] ],
firstSlidePosition = parseInt( firstSlide.style[ positionProperty ], 10 ),
lastSlide = this.slider.slidesEl.getElementsByClassName( 'sp-slide' )[ this.slider.slidesOrder[ this.slider.slidesOrder.length - 1 ] ],
lastSlidePosition = parseInt( lastSlide.style[ positionProperty ], 10 ) + ( this.slider.settings.rightToLeft === true && this.slider.settings.orientation === 'horizontal' ? -1 : 1 ) * parseInt( window.getComputedStyle( lastSlide )[ sizeProperty ], 10 );
slidesSize = Math.abs( lastSlidePosition - firstSlidePosition );
averageSlideSize = Math.round( slidesSize / this.slider.slides.length );
} else {
slidesSize = ( ( this.slider.settings.orientation === 'horizontal' ? this.slider.slideWidth : this.slider.slideHeight ) + this.slider.settings.slideDistance ) * this.slider.slides.length - this.slider.settings.slideDistance;
averageSlideSize = this.slider.settings.orientation === 'horizontal' ? this.slider.slideWidth : this.slider.slideHeight;
}
// Use either the middle position or the index of the selected slide as a reference, depending on
// whether the slider is loop-able
const referencePosition = this.slider.settings.loop === true ? middleSlidePosition : this.slider.selectedSlideIndex,
// Calculate how many slides are visible at the sides of the selected slide
visibleOnSides = Math.ceil( ( parseInt( this.slider.slidesMaskEl.style[ sizeProperty ], 10) - averageSlideSize ) / 2 / averageSlideSize ),
// Calculate the indexes of the first and last slide that will be checked
from = this.slider.settings.centerSelectedSlide === true ? Math.max( referencePosition - visibleOnSides - 1, 0 ) : Math.max( referencePosition - 1, 0 ),
to = this.slider.settings.centerSelectedSlide === true ? Math.min( referencePosition + visibleOnSides + 1, this.slider.getTotalSlides() - 1 ) : Math.min( referencePosition + visibleOnSides * 2 + 1, this.slider.getTotalSlides() - 1 ),
// Get all the slides that need to be checked
slidesToCheck = this.slider.slidesOrder.slice( from, to + 1 );
// Loop through the selected slides and if the slide is not marked as having
// been loaded yet, loop through its images and load them.
slidesToCheck.forEach( ( slideIndex ) => {
const slide = this.slider.slides[ slideIndex ],
slideEl = slide.slideEl;
if ( slideEl.getAttribute( 'data-loaded' ) === null ) {
slideEl.setAttribute( 'data-loaded', true );
Array.from( slideEl.querySelectorAll( 'img[ data-src ]' ) ).forEach(( imgEl ) => {
this.loadImage( imgEl, ( newImageEl ) => {
if ( newImageEl.classList.contains( 'sp-image' ) ) {
slide.mainImageEl = newImageEl;
slide.resizeMainImage( true );
}
});
});
}
});
}
// Check visible thumbnails and load their images
checkAndLoadVisibleThumbnailImages() {
if ( this.slider.sliderEl.querySelectorAll( '.sp-thumbnail-container:not([ data-loaded ])' ).length === 0 ) {
return;
}
const thumbnailSize = this.slider.thumbnails.thumbnailsSize / this.slider.thumbnails.thumbnails.length,
// Calculate the indexes of the first and last thumbnail that will be checked
from = Math.floor( Math.abs( this.slider.thumbnails.thumbnailsPosition / thumbnailSize ) ),
to = Math.floor( ( - this.slider.thumbnails.thumbnailsPosition + this.slider.thumbnails.thumbnailsContainerSize ) / thumbnailSize ),
// Get all the thumbnails that need to be checked
thumbnailsToCheck = this.slider.thumbnails.thumbnails.slice( from, to + 1 );
// Loop through the selected thumbnails and if the thumbnail is not marked as having
// been loaded yet, load its image.
thumbnailsToCheck.forEach( ( thumbnail ) => {
const thumbnailContainerEl = thumbnail.thumbnailContainerEl;
if ( thumbnailContainerEl.getAttribute( 'data-loaded' ) === null ) {
thumbnailContainerEl.setAttribute( 'data-loaded', true );
Array.from( thumbnailContainerEl.querySelectorAll( 'img[ data-src ]' ) ).forEach( ( imageEl) => {
this.loadImage( imageEl, () => {
thumbnail.resizeImage();
});
});
}
});
}
// Load an image
loadImage( imageEl, callback ) {
// Create a new image element
const newImageEl = new Image();
// Copy the class(es) and inline style
newImageEl.setAttribute( 'class', imageEl.getAttribute( 'class' ) );
newImageEl.setAttribute( 'style', imageEl.getAttribute( 'style' ) );
// Copy the data attributes
for ( let keyname in imageEl.dataset ) {
newImageEl.setAttribute( 'data-' + keyname, imageEl.dataset[ keyname ] );
}
// Copy the width and height attributes if they exist
if ( imageEl.getAttribute( 'width' ) !== null ) {
newImageEl.setAttribute( 'width', imageEl.getAttribute( 'width' ) );
}
if ( imageEl.getAttribute( 'height' ) !== null ) {
newImageEl.setAttribute( 'height', imageEl.getAttribute( 'height' ) );
}
if ( imageEl.getAttribute( 'alt' ) !== null ) {
newImageEl.setAttribute( 'alt', imageEl.getAttribute( 'alt' ) );
}
if ( imageEl.getAttribute( 'title' ) !== null ) {
newImageEl.setAttribute( 'title', imageEl.getAttribute( 'title' ) );
}
// Assign the source of the image
newImageEl.setAttribute( 'src', imageEl.getAttribute( 'data-src' ) );
newImageEl.removeAttribute( 'data-src' );
if ( imageEl.getAttribute( 'data-srcset' ) !== null ) {
newImageEl.setAttribute( 'srcset', imageEl.getAttribute( 'data-srcset' ) );
newImageEl.removeAttribute( 'data-srcset' );
}
// Add the new image in the same container and remove the older image
imageEl.after( newImageEl );
imageEl.remove();
imageEl = null;
if ( typeof callback === 'function' ) {
callback( newImageEl );
}
}
// Destroy the module
destroy() {
this.slider.removeEventListener( 'update.' + this.namespace );
this.slider.removeEventListener( 'gotoSlide.' + this.namespace );
this.slider.removeEventListener( 'resize.' + this.namespace );
this.slider.thumbnails.removeEventListener( 'thumbnailsUpdate.' + this.namespace );
this.slider.thumbnails.removeEventListener( 'thumbnailsMoveComplete.' + this.namespace );
}
}
export default LazyLoading;