imagesloaded.pkgd.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. /*!
  2. Images Loaded Packaged
  3. Version: v5.0.0
  4. Plugin URL: https://imagesloaded.desandro.com/
  5. License: Copyright 2021-2024 | Released under the MIT License
  6. !*/
  7. /**
  8. * EvEmitter v2.1.1
  9. * Lil' event emitter
  10. * MIT License
  11. */
  12. ( function( global, factory ) {
  13. // universal module definition
  14. if ( typeof module == 'object' && module.exports ) {
  15. // CommonJS - Browserify, Webpack
  16. module.exports = factory();
  17. } else {
  18. // Browser globals
  19. global.EvEmitter = factory();
  20. }
  21. }( typeof window != 'undefined' ? window : this, function() {
  22. function EvEmitter() {}
  23. let proto = EvEmitter.prototype;
  24. proto.on = function( eventName, listener ) {
  25. if ( !eventName || !listener ) return this;
  26. // set events hash
  27. let events = this._events = this._events || {};
  28. // set listeners array
  29. let listeners = events[ eventName ] = events[ eventName ] || [];
  30. // only add once
  31. if ( !listeners.includes( listener ) ) {
  32. listeners.push( listener );
  33. }
  34. return this;
  35. };
  36. proto.once = function( eventName, listener ) {
  37. if ( !eventName || !listener ) return this;
  38. // add event
  39. this.on( eventName, listener );
  40. // set once flag
  41. // set onceEvents hash
  42. let onceEvents = this._onceEvents = this._onceEvents || {};
  43. // set onceListeners object
  44. let onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
  45. // set flag
  46. onceListeners[ listener ] = true;
  47. return this;
  48. };
  49. proto.off = function( eventName, listener ) {
  50. let listeners = this._events && this._events[ eventName ];
  51. if ( !listeners || !listeners.length ) return this;
  52. let index = listeners.indexOf( listener );
  53. if ( index != -1 ) {
  54. listeners.splice( index, 1 );
  55. }
  56. return this;
  57. };
  58. proto.emitEvent = function( eventName, args ) {
  59. let listeners = this._events && this._events[ eventName ];
  60. if ( !listeners || !listeners.length ) return this;
  61. // copy over to avoid interference if .off() in listener
  62. listeners = listeners.slice( 0 );
  63. args = args || [];
  64. // once stuff
  65. let onceListeners = this._onceEvents && this._onceEvents[ eventName ];
  66. for ( let listener of listeners ) {
  67. let isOnce = onceListeners && onceListeners[ listener ];
  68. if ( isOnce ) {
  69. // remove listener
  70. // remove before trigger to prevent recursion
  71. this.off( eventName, listener );
  72. // unset once flag
  73. delete onceListeners[ listener ];
  74. }
  75. // trigger listener
  76. listener.apply( this, args );
  77. }
  78. return this;
  79. };
  80. proto.allOff = function() {
  81. delete this._events;
  82. delete this._onceEvents;
  83. return this;
  84. };
  85. return EvEmitter;
  86. } ) );
  87. /*!
  88. * imagesLoaded v5.0.0
  89. * JavaScript is all like "You images are done yet or what?"
  90. * MIT License
  91. */
  92. ( function( window, factory ) {
  93. // universal module definition
  94. if ( typeof module == 'object' && module.exports ) {
  95. // CommonJS
  96. module.exports = factory( window, require('ev-emitter') );
  97. } else {
  98. // browser global
  99. window.imagesLoaded = factory( window, window.EvEmitter );
  100. }
  101. } )( typeof window !== 'undefined' ? window : this,
  102. function factory( window, EvEmitter ) {
  103. let $ = window.jQuery;
  104. let console = window.console;
  105. // -------------------------- helpers -------------------------- //
  106. // turn element or nodeList into an array
  107. function makeArray( obj ) {
  108. // use object if already an array
  109. if ( Array.isArray( obj ) ) return obj;
  110. let isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
  111. // convert nodeList to array
  112. if ( isArrayLike ) return [ ...obj ];
  113. // array of single index
  114. return [ obj ];
  115. }
  116. // -------------------------- imagesLoaded -------------------------- //
  117. /**
  118. * @param {[Array, Element, NodeList, String]} elem
  119. * @param {[Object, Function]} options - if function, use as callback
  120. * @param {Function} onAlways - callback function
  121. * @returns {ImagesLoaded}
  122. */
  123. function ImagesLoaded( elem, options, onAlways ) {
  124. // coerce ImagesLoaded() without new, to be new ImagesLoaded()
  125. if ( !( this instanceof ImagesLoaded ) ) {
  126. return new ImagesLoaded( elem, options, onAlways );
  127. }
  128. // use elem as selector string
  129. let queryElem = elem;
  130. if ( typeof elem == 'string' ) {
  131. queryElem = document.querySelectorAll( elem );
  132. }
  133. // bail if bad element
  134. if ( !queryElem ) {
  135. console.error(`Bad element for imagesLoaded ${queryElem || elem}`);
  136. return;
  137. }
  138. this.elements = makeArray( queryElem );
  139. this.options = {};
  140. // shift arguments if no options set
  141. if ( typeof options == 'function' ) {
  142. onAlways = options;
  143. } else {
  144. Object.assign( this.options, options );
  145. }
  146. if ( onAlways ) this.on( 'always', onAlways );
  147. this.getImages();
  148. // add jQuery Deferred object
  149. if ( $ ) this.jqDeferred = new $.Deferred();
  150. // HACK check async to allow time to bind listeners
  151. setTimeout( this.check.bind( this ) );
  152. }
  153. ImagesLoaded.prototype = Object.create( EvEmitter.prototype );
  154. ImagesLoaded.prototype.getImages = function() {
  155. this.images = [];
  156. // filter & find items if we have an item selector
  157. this.elements.forEach( this.addElementImages, this );
  158. };
  159. const elementNodeTypes = [ 1, 9, 11 ];
  160. /**
  161. * @param {Node} elem
  162. */
  163. ImagesLoaded.prototype.addElementImages = function( elem ) {
  164. // filter siblings
  165. if ( elem.nodeName === 'IMG' ) {
  166. this.addImage( elem );
  167. }
  168. // get background image on element
  169. if ( this.options.background === true ) {
  170. this.addElementBackgroundImages( elem );
  171. }
  172. // find children
  173. // no non-element nodes, #143
  174. let { nodeType } = elem;
  175. if ( !nodeType || !elementNodeTypes.includes( nodeType ) ) return;
  176. let childImgs = elem.querySelectorAll('img');
  177. // concat childElems to filterFound array
  178. for ( let img of childImgs ) {
  179. this.addImage( img );
  180. }
  181. // get child background images
  182. if ( typeof this.options.background == 'string' ) {
  183. let children = elem.querySelectorAll( this.options.background );
  184. for ( let child of children ) {
  185. this.addElementBackgroundImages( child );
  186. }
  187. }
  188. };
  189. const reURL = /url\((['"])?(.*?)\1\)/gi;
  190. ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
  191. let style = getComputedStyle( elem );
  192. // Firefox returns null if in a hidden iframe https://bugzil.la/548397
  193. if ( !style ) return;
  194. // get url inside url("...")
  195. let matches = reURL.exec( style.backgroundImage );
  196. while ( matches !== null ) {
  197. let url = matches && matches[2];
  198. if ( url ) {
  199. this.addBackground( url, elem );
  200. }
  201. matches = reURL.exec( style.backgroundImage );
  202. }
  203. };
  204. /**
  205. * @param {Image} img
  206. */
  207. ImagesLoaded.prototype.addImage = function( img ) {
  208. let loadingImage = new LoadingImage( img );
  209. this.images.push( loadingImage );
  210. };
  211. ImagesLoaded.prototype.addBackground = function( url, elem ) {
  212. let background = new Background( url, elem );
  213. this.images.push( background );
  214. };
  215. ImagesLoaded.prototype.check = function() {
  216. this.progressedCount = 0;
  217. this.hasAnyBroken = false;
  218. // complete if no images
  219. if ( !this.images.length ) {
  220. this.complete();
  221. return;
  222. }
  223. /* eslint-disable-next-line func-style */
  224. let onProgress = ( image, elem, message ) => {
  225. // HACK - Chrome triggers event before object properties have changed. #83
  226. setTimeout( () => {
  227. this.progress( image, elem, message );
  228. } );
  229. };
  230. this.images.forEach( function( loadingImage ) {
  231. loadingImage.once( 'progress', onProgress );
  232. loadingImage.check();
  233. } );
  234. };
  235. ImagesLoaded.prototype.progress = function( image, elem, message ) {
  236. this.progressedCount++;
  237. this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
  238. // progress event
  239. this.emitEvent( 'progress', [ this, image, elem ] );
  240. if ( this.jqDeferred && this.jqDeferred.notify ) {
  241. this.jqDeferred.notify( this, image );
  242. }
  243. // check if completed
  244. if ( this.progressedCount === this.images.length ) {
  245. this.complete();
  246. }
  247. if ( this.options.debug && console ) {
  248. console.log( `progress: ${message}`, image, elem );
  249. }
  250. };
  251. ImagesLoaded.prototype.complete = function() {
  252. let eventName = this.hasAnyBroken ? 'fail' : 'done';
  253. this.isComplete = true;
  254. this.emitEvent( eventName, [ this ] );
  255. this.emitEvent( 'always', [ this ] );
  256. if ( this.jqDeferred ) {
  257. let jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
  258. this.jqDeferred[ jqMethod ]( this );
  259. }
  260. };
  261. // -------------------------- -------------------------- //
  262. function LoadingImage( img ) {
  263. this.img = img;
  264. }
  265. LoadingImage.prototype = Object.create( EvEmitter.prototype );
  266. LoadingImage.prototype.check = function() {
  267. // If complete is true and browser supports natural sizes,
  268. // try to check for image status manually.
  269. let isComplete = this.getIsImageComplete();
  270. if ( isComplete ) {
  271. // report based on naturalWidth
  272. this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
  273. return;
  274. }
  275. // If none of the checks above matched, simulate loading on detached element.
  276. this.proxyImage = new Image();
  277. // add crossOrigin attribute. #204
  278. if ( this.img.crossOrigin ) {
  279. this.proxyImage.crossOrigin = this.img.crossOrigin;
  280. }
  281. this.proxyImage.addEventListener( 'load', this );
  282. this.proxyImage.addEventListener( 'error', this );
  283. // bind to image as well for Firefox. #191
  284. this.img.addEventListener( 'load', this );
  285. this.img.addEventListener( 'error', this );
  286. this.proxyImage.src = this.img.currentSrc || this.img.src;
  287. };
  288. LoadingImage.prototype.getIsImageComplete = function() {
  289. // check for non-zero, non-undefined naturalWidth
  290. // fixes Safari+InfiniteScroll+Masonry bug infinite-scroll#671
  291. return this.img.complete && this.img.naturalWidth;
  292. };
  293. LoadingImage.prototype.confirm = function( isLoaded, message ) {
  294. this.isLoaded = isLoaded;
  295. let { parentNode } = this.img;
  296. // emit progress with parent <picture> or self <img>
  297. let elem = parentNode.nodeName === 'PICTURE' ? parentNode : this.img;
  298. this.emitEvent( 'progress', [ this, elem, message ] );
  299. };
  300. // ----- events ----- //
  301. // trigger specified handler for event type
  302. LoadingImage.prototype.handleEvent = function( event ) {
  303. let method = 'on' + event.type;
  304. if ( this[ method ] ) {
  305. this[ method ]( event );
  306. }
  307. };
  308. LoadingImage.prototype.onload = function() {
  309. this.confirm( true, 'onload' );
  310. this.unbindEvents();
  311. };
  312. LoadingImage.prototype.onerror = function() {
  313. this.confirm( false, 'onerror' );
  314. this.unbindEvents();
  315. };
  316. LoadingImage.prototype.unbindEvents = function() {
  317. this.proxyImage.removeEventListener( 'load', this );
  318. this.proxyImage.removeEventListener( 'error', this );
  319. this.img.removeEventListener( 'load', this );
  320. this.img.removeEventListener( 'error', this );
  321. };
  322. // -------------------------- Background -------------------------- //
  323. function Background( url, element ) {
  324. this.url = url;
  325. this.element = element;
  326. this.img = new Image();
  327. }
  328. // inherit LoadingImage prototype
  329. Background.prototype = Object.create( LoadingImage.prototype );
  330. Background.prototype.check = function() {
  331. this.img.addEventListener( 'load', this );
  332. this.img.addEventListener( 'error', this );
  333. this.img.src = this.url;
  334. // check if image is already complete
  335. let isComplete = this.getIsImageComplete();
  336. if ( isComplete ) {
  337. this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
  338. this.unbindEvents();
  339. }
  340. };
  341. Background.prototype.unbindEvents = function() {
  342. this.img.removeEventListener( 'load', this );
  343. this.img.removeEventListener( 'error', this );
  344. };
  345. Background.prototype.confirm = function( isLoaded, message ) {
  346. this.isLoaded = isLoaded;
  347. this.emitEvent( 'progress', [ this, this.element, message ] );
  348. };
  349. // -------------------------- jQuery -------------------------- //
  350. ImagesLoaded.makeJQueryPlugin = function( jQuery ) {
  351. jQuery = jQuery || window.jQuery;
  352. if ( !jQuery ) return;
  353. // set local variable
  354. $ = jQuery;
  355. // $().imagesLoaded()
  356. $.fn.imagesLoaded = function( options, onAlways ) {
  357. let instance = new ImagesLoaded( this, options, onAlways );
  358. return instance.jqDeferred.promise( $( this ) );
  359. };
  360. };
  361. // try making plugin
  362. ImagesLoaded.makeJQueryPlugin();
  363. // -------------------------- -------------------------- //
  364. return ImagesLoaded;
  365. } );