UNPKG

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