/*! * imagesLoaded v3.2.0 * JavaScript is all like "You images are done yet or what?" * MIT License */ ( function( window, factory ) { 'use strict'; // universal module definition /*global define: false, module: false, require: false */ if ( typeof define == 'function' && define.amd ) { // AMD define( [ 'eventEmitter/EventEmitter', 'eventie/eventie' ], function( EventEmitter, eventie ) { return factory( window, EventEmitter, eventie ); }); } else if ( typeof module == 'object' && module.exports ) { // CommonJS module.exports = factory( window, require('wolfy87-eventemitter'), require('eventie') ); } else { // browser global window.imagesLoaded = factory( window, window.EventEmitter, window.eventie ); } })( window, // -------------------------- factory -------------------------- // function factory( window, EventEmitter, eventie ) { 'use strict'; var $ = window.jQuery; var console = window.console; // -------------------------- helpers -------------------------- // // extend objects function extend( a, b ) { for ( var prop in b ) { a[ prop ] = b[ prop ]; } return a; } var objToString = Object.prototype.toString; function isArray( obj ) { return objToString.call( obj ) == '[object Array]'; } // turn element or nodeList into an array function makeArray( obj ) { var ary = []; if ( isArray( obj ) ) { // use object if already an array ary = obj; } else if ( typeof obj.length == 'number' ) { // convert nodeList to array for ( var i=0; i < obj.length; i++ ) { ary.push( obj[i] ); } } else { // array of single index ary.push( obj ); } return ary; } // -------------------------- imagesLoaded -------------------------- // /** * @param {Array, Element, NodeList, String} elem * @param {Object or Function} options - if function, use as callback * @param {Function} onAlways - callback function */ function ImagesLoaded( elem, options, onAlways ) { // coerce ImagesLoaded() without new, to be new ImagesLoaded() if ( !( this instanceof ImagesLoaded ) ) { return new ImagesLoaded( elem, options, onAlways ); } // use elem as selector string if ( typeof elem == 'string' ) { elem = document.querySelectorAll( elem ); } this.elements = makeArray( elem ); this.options = extend( {}, this.options ); if ( typeof options == 'function' ) { onAlways = options; } else { extend( this.options, options ); } if ( onAlways ) { this.on( 'always', onAlways ); } this.getImages(); if ( $ ) { // add jQuery Deferred object this.jqDeferred = new $.Deferred(); } // HACK check async to allow time to bind listeners var _this = this; setTimeout( function() { _this.check(); }); } ImagesLoaded.prototype = new EventEmitter(); ImagesLoaded.prototype.options = {}; ImagesLoaded.prototype.getImages = function() { this.images = []; // filter & find items if we have an item selector for ( var i=0; i < this.elements.length; i++ ) { var elem = this.elements[i]; this.addElementImages( elem ); } }; /** * @param {Node} element */ ImagesLoaded.prototype.addElementImages = function( elem ) { // filter siblings if ( elem.nodeName == 'IMG' ) { this.addImage( elem ); } // get background image on element if ( this.options.background === true ) { this.addElementBackgroundImages( elem ); } // find children // no non-element nodes, #143 var nodeType = elem.nodeType; if ( !nodeType || !elementNodeTypes[ nodeType ] ) { return; } var childImgs = elem.querySelectorAll('img'); // concat childElems to filterFound array for ( var i=0; i < childImgs.length; i++ ) { var img = childImgs[i]; this.addImage( img ); } // get child background images if ( typeof this.options.background == 'string' ) { var children = elem.querySelectorAll( this.options.background ); for ( i=0; i < children.length; i++ ) { var child = children[i]; this.addElementBackgroundImages( child ); } } }; var elementNodeTypes = { 1: true, 9: true, 11: true }; ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) { var style = getStyle( elem ); // get url inside url("...") var reURL = /url\(['"]*([^'"\)]+)['"]*\)/gi; var matches = reURL.exec( style.backgroundImage ); while ( matches !== null ) { var url = matches && matches[1]; if ( url ) { this.addBackground( url, elem ); } matches = reURL.exec( style.backgroundImage ); } }; // IE8 var getStyle = window.getComputedStyle || function( elem ) { return elem.currentStyle; }; /** * @param {Image} img */ ImagesLoaded.prototype.addImage = function( img ) { var loadingImage = new LoadingImage( img ); this.images.push( loadingImage ); }; ImagesLoaded.prototype.addBackground = function( url, elem ) { var background = new Background( url, elem ); this.images.push( background ); }; ImagesLoaded.prototype.check = function() { var _this = this; this.progressedCount = 0; this.hasAnyBroken = false; // complete if no images if ( !this.images.length ) { this.complete(); return; } function onProgress( image, elem, message ) { // HACK - Chrome triggers event before object properties have changed. #83 setTimeout( function() { _this.progress( image, elem, message ); }); } for ( var i=0; i < this.images.length; i++ ) { var loadingImage = this.images[i]; loadingImage.once( 'progress', onProgress ); loadingImage.check(); } }; ImagesLoaded.prototype.progress = function( image, elem, message ) { this.progressedCount++; this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded; // progress event this.emit( 'progress', this, image, elem ); if ( this.jqDeferred && this.jqDeferred.notify ) { this.jqDeferred.notify( this, image ); } // check if completed if ( this.progressedCount == this.images.length ) { this.complete(); } if ( this.options.debug && console ) { console.log( 'progress: ' + message, image, elem ); } }; ImagesLoaded.prototype.complete = function() { var eventName = this.hasAnyBroken ? 'fail' : 'done'; this.isComplete = true; this.emit( eventName, this ); this.emit( 'always', this ); if ( this.jqDeferred ) { var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve'; this.jqDeferred[ jqMethod ]( this ); } }; // -------------------------- -------------------------- // function LoadingImage( img ) { this.img = img; } LoadingImage.prototype = new EventEmitter(); LoadingImage.prototype.check = function() { // If complete is true and browser supports natural sizes, // try to check for image status manually. var isComplete = this.getIsImageComplete(); if ( isComplete ) { // report based on naturalWidth this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' ); return; } // If none of the checks above matched, simulate loading on detached element. this.proxyImage = new Image(); eventie.bind( this.proxyImage, 'load', this ); eventie.bind( this.proxyImage, 'error', this ); // bind to image as well for Firefox. #191 eventie.bind( this.img, 'load', this ); eventie.bind( this.img, 'error', this ); this.proxyImage.src = this.img.src; }; LoadingImage.prototype.getIsImageComplete = function() { return this.img.complete && this.img.naturalWidth !== undefined; }; LoadingImage.prototype.confirm = function( isLoaded, message ) { this.isLoaded = isLoaded; this.emit( 'progress', this, this.img, message ); }; // ----- events ----- // // trigger specified handler for event type LoadingImage.prototype.handleEvent = function( event ) { var method = 'on' + event.type; if ( this[ method ] ) { this[ method ]( event ); } }; LoadingImage.prototype.onload = function() { this.confirm( true, 'onload' ); this.unbindEvents(); }; LoadingImage.prototype.onerror = function() { this.confirm( false, 'onerror' ); this.unbindEvents(); }; LoadingImage.prototype.unbindEvents = function() { eventie.unbind( this.proxyImage, 'load', this ); eventie.unbind( this.proxyImage, 'error', this ); eventie.unbind( this.img, 'load', this ); eventie.unbind( this.img, 'error', this ); }; // -------------------------- Background -------------------------- // function Background( url, element ) { this.url = url; this.element = element; this.img = new Image(); } // inherit LoadingImage prototype Background.prototype = new LoadingImage(); Background.prototype.check = function() { eventie.bind( this.img, 'load', this ); eventie.bind( this.img, 'error', this ); this.img.src = this.url; // check if image is already complete var isComplete = this.getIsImageComplete(); if ( isComplete ) { this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' ); this.unbindEvents(); } }; Background.prototype.unbindEvents = function() { eventie.unbind( this.img, 'load', this ); eventie.unbind( this.img, 'error', this ); }; Background.prototype.confirm = function( isLoaded, message ) { this.isLoaded = isLoaded; this.emit( 'progress', this, this.element, message ); }; // -------------------------- jQuery -------------------------- // ImagesLoaded.makeJQueryPlugin = function( jQuery ) { jQuery = jQuery || window.jQuery; if ( !jQuery ) { return; } // set local variable $ = jQuery; // $().imagesLoaded() $.fn.imagesLoaded = function( options, callback ) { var instance = new ImagesLoaded( this, options, callback ); return instance.jqDeferred.promise( $(this) ); }; }; // try making plugin ImagesLoaded.makeJQueryPlugin(); // -------------------------- -------------------------- // return ImagesLoaded; });