LazyLoad.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import { ROLE } from '../../constants/attributes';
  2. import { CLASS_LOADING } from '../../constants/classes';
  3. import {
  4. EVENT_LAZYLOAD_LOADED,
  5. EVENT_MOUNTED,
  6. EVENT_MOVED,
  7. EVENT_REFRESH,
  8. EVENT_RESIZE,
  9. } from '../../constants/events';
  10. import { EventInterface } from '../../constructors';
  11. import { Splide } from '../../core/Splide/Splide';
  12. import { BaseComponent, Components, Options } from '../../types';
  13. import {
  14. addClass, child,
  15. create,
  16. display,
  17. getAttribute,
  18. queryAll,
  19. remove,
  20. removeAttribute,
  21. removeClass,
  22. setAttribute,
  23. } from '../../utils';
  24. import { SlideComponent } from '../Slides/Slide';
  25. import { IMAGE_SELECTOR, SRC_DATA_ATTRIBUTE, SRCSET_DATA_ATTRIBUTE } from './constants';
  26. /**
  27. * The interface for the LazyLoad component.
  28. *
  29. * @since 3.0.0
  30. */
  31. export interface LazyLoadComponent extends BaseComponent {
  32. }
  33. /**
  34. * The interface for all components.
  35. *
  36. * @since 3.0.0
  37. */
  38. export interface LazyLoadImagesData {
  39. _img: HTMLImageElement;
  40. _spinner: HTMLSpanElement;
  41. _Slide: SlideComponent;
  42. src: string | null;
  43. srcset: string | null;
  44. }
  45. /**
  46. * The component for lazily loading images.
  47. *
  48. * @since 3.0.0
  49. *
  50. * @param Splide - A Splide instance.
  51. * @param Components - A collection of components.
  52. * @param options - Options.
  53. *
  54. * @return An LazyLoad component object.
  55. */
  56. export function LazyLoad( Splide: Splide, Components: Components, options: Options ): LazyLoadComponent {
  57. const { on, off, bind, emit } = EventInterface( Splide );
  58. const isSequential = options.lazyLoad === 'sequential';
  59. /**
  60. * Stores data of images.
  61. */
  62. let images: LazyLoadImagesData[] = [];
  63. /**
  64. * The current index of images.
  65. */
  66. let index = 0;
  67. /**
  68. * Called when the component is mounted.
  69. */
  70. function mount(): void {
  71. if ( options.lazyLoad ) {
  72. on( [ EVENT_MOUNTED, EVENT_REFRESH ], () => {
  73. destroy();
  74. init();
  75. } );
  76. if ( ! isSequential ) {
  77. on( [ EVENT_MOUNTED, EVENT_REFRESH, EVENT_MOVED ], observe );
  78. }
  79. }
  80. }
  81. /**
  82. * Finds images that contain specific data attributes.
  83. */
  84. function init() {
  85. Components.Slides.forEach( _Slide => {
  86. queryAll<HTMLImageElement>( _Slide.slide, IMAGE_SELECTOR ).forEach( _img => {
  87. const src = getAttribute( _img, SRC_DATA_ATTRIBUTE );
  88. const srcset = getAttribute( _img, SRCSET_DATA_ATTRIBUTE );
  89. if ( src !== _img.src || srcset !== _img.srcset ) {
  90. const className = options.classes.spinner;
  91. const parent = _img.parentElement;
  92. const _spinner = child( parent, className ) || create( 'span', className, parent );
  93. setAttribute( _spinner, ROLE, 'presentation' );
  94. images.push( { _img, _Slide, src, srcset, _spinner } );
  95. ! _img.src && display( _img, 'none' );
  96. }
  97. } );
  98. } );
  99. if ( isSequential ) {
  100. loadNext();
  101. }
  102. }
  103. /**
  104. * Destroys the component.
  105. */
  106. function destroy() {
  107. index = 0;
  108. images = [];
  109. }
  110. /**
  111. * Checks how close each image is from the active slide, and determines whether to start loading or not.
  112. * The last `+1` is for the current page.
  113. */
  114. function observe(): void {
  115. images = images.filter( data => {
  116. const distance = options.perPage * ( ( options.preloadPages || 1 ) + 1 ) - 1;
  117. if ( data._Slide.isWithin( Splide.index, distance ) ) {
  118. return load( data );
  119. }
  120. return true;
  121. } );
  122. if ( ! images.length ) {
  123. off( EVENT_MOVED );
  124. }
  125. }
  126. /**
  127. * Starts loading the image in the data.
  128. *
  129. * @param data - A LazyLoadImagesData object.
  130. */
  131. function load( data: LazyLoadImagesData ): void {
  132. const { _img } = data;
  133. addClass( data._Slide.slide, CLASS_LOADING );
  134. bind( _img, 'load error', e => { onLoad( data, e.type === 'error' ) } );
  135. [ 'src', 'srcset' ].forEach( name => {
  136. if ( data[ name ] ) {
  137. setAttribute( _img, name, data[ name ] );
  138. removeAttribute( _img, name === 'src' ? SRC_DATA_ATTRIBUTE : SRCSET_DATA_ATTRIBUTE );
  139. }
  140. } );
  141. }
  142. /**
  143. * Called when the image is loaded or any error occurs.
  144. *
  145. * @param data - A LazyLoadImagesData object.
  146. * @param error - `true` if this method is called on error.
  147. */
  148. function onLoad( data: LazyLoadImagesData, error: boolean ): void {
  149. const { _Slide } = data;
  150. removeClass( _Slide.slide, CLASS_LOADING );
  151. if ( ! error ) {
  152. remove( data._spinner );
  153. display( data._img, '' );
  154. emit( EVENT_LAZYLOAD_LOADED, data._img, _Slide );
  155. emit( EVENT_RESIZE );
  156. }
  157. if ( isSequential ) {
  158. loadNext();
  159. }
  160. }
  161. /**
  162. * Starts loading a next image.
  163. */
  164. function loadNext(): void {
  165. if ( index < images.length ) {
  166. load( images[ index++ ] );
  167. }
  168. }
  169. return {
  170. mount,
  171. destroy,
  172. };
  173. }