index.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. /**
  2. * The component for loading slider images lazily.
  3. *
  4. * @author Naotoshi Fujita
  5. * @copyright Naotoshi Fujita. All rights reserved.
  6. */
  7. import { STATUS_CLASSES } from '../../constants/classes';
  8. import {
  9. create,
  10. remove,
  11. append,
  12. find,
  13. addClass,
  14. removeClass,
  15. setAttribute,
  16. getAttribute,
  17. applyStyle,
  18. } from '../../utils/dom';
  19. /**
  20. * The name for a data attribute.
  21. *
  22. * @type {string}
  23. */
  24. const SRC_DATA_NAME = 'data-splide-lazy';
  25. /**
  26. * The component for loading slider images lazily.
  27. *
  28. * @param {Splide} Splide - A Splide instance.
  29. * @param {Object} Components - An object containing components.
  30. * @param {string} name - A component name as a lowercase string.
  31. *
  32. * @return {Object} - The component object.
  33. */
  34. export default ( Splide, Components, name ) => {
  35. /**
  36. * Next index for sequential loading.
  37. *
  38. * @type {number}
  39. */
  40. let nextIndex = 0;
  41. /**
  42. * Store objects containing an img element and a Slide object.
  43. *
  44. * @type {Object[]}
  45. */
  46. let images = [];
  47. /**
  48. * Store a lazyload option value.
  49. *
  50. * @type {string|boolean}
  51. */
  52. const lazyload = Splide.options.lazyLoad;
  53. /**
  54. * Whether to load images sequentially or not.
  55. *
  56. * @type {boolean}
  57. */
  58. const isSequential = lazyload === 'sequential';
  59. /**
  60. * Lazyload component object.
  61. *
  62. * @type {Object}
  63. */
  64. const Lazyload = {
  65. /**
  66. * Mount only when the lazyload option is provided.
  67. *
  68. * @type {boolean}
  69. */
  70. required: lazyload,
  71. /**
  72. * Called when the component is mounted.
  73. */
  74. mount() {
  75. Components.Elements.each( Slide => {
  76. const img = find( Slide.slide, `[${ SRC_DATA_NAME }]` );
  77. if ( img ) {
  78. images.push( { img, Slide } );
  79. applyStyle( img, { display: 'none' } );
  80. }
  81. } );
  82. if ( images.length ) {
  83. if ( isSequential ) {
  84. loadNext();
  85. } else {
  86. Splide.on( `mounted moved.${ name }`, index => { check( index || Splide.index ) } );
  87. }
  88. }
  89. },
  90. /**
  91. * Destroy.
  92. */
  93. destroy() {
  94. images = [];
  95. },
  96. };
  97. /**
  98. * Check how close each image is from the active slide and
  99. * determine whether to start loading or not, according to the distance.
  100. *
  101. * @param {number} index - Current index.
  102. */
  103. function check( index ) {
  104. const options = Splide.options;
  105. images = images.filter( image => {
  106. if ( image.Slide.isWithin( index, options.perPage * ( options.preloadPages + 1 ) ) ) {
  107. load( image.img, image.Slide );
  108. return false;
  109. }
  110. return true;
  111. } );
  112. // Unbind if all images are loaded.
  113. if ( ! images.length ) {
  114. Splide.off( `moved.${ name }` );
  115. }
  116. }
  117. /**
  118. * Start loading an image.
  119. * Creating a clone of the image element since setting src attribute directly to it
  120. * often occurs 'hitch', blocking some other processes of a browser.
  121. *
  122. * @param {Element} img - An image element.
  123. * @param {Object} Slide - A Slide object.
  124. */
  125. function load( img, Slide ) {
  126. addClass( Slide.slide, STATUS_CLASSES.loading );
  127. const spinner = create( 'span', { class: Splide.classes.spinner } );
  128. append( img.parentElement, spinner );
  129. img.onload = () => { loaded( img, spinner, Slide, false ) };
  130. img.onerror = () => { loaded( img, spinner, Slide, true ) };
  131. setAttribute( img, 'src', getAttribute( img, SRC_DATA_NAME ) );
  132. }
  133. /**
  134. * Start loading a next image in images array.
  135. */
  136. function loadNext() {
  137. if ( nextIndex < images.length ) {
  138. const image = images[ nextIndex ];
  139. load( image.img, image.Slide );
  140. }
  141. nextIndex++;
  142. }
  143. /**
  144. * Called just after the image was loaded or loading was aborted by some error.
  145. *
  146. * @param {Element} img - An image element.
  147. * @param {Element} spinner - A spinner element.
  148. * @param {Object} Slide - A Slide object.
  149. * @param {boolean} error - True if the image was loaded successfully or false on error.
  150. */
  151. function loaded( img, spinner, Slide, error ) {
  152. removeClass( Slide.slide, STATUS_CLASSES.loading );
  153. if ( ! error ) {
  154. remove( spinner );
  155. applyStyle( img, { display: '' } );
  156. Splide.emit( `${ name }:loaded`, img ).emit( 'resize' );
  157. }
  158. if ( isSequential ) {
  159. loadNext();
  160. }
  161. }
  162. return Lazyload;
  163. }