imagesloaded.js 9.7 KB

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