Repository: desandro/imagesloaded
Branch: master
Commit: 92de29b5a9a4
Files: 32
Total size: 60.0 KB
Directory structure:
gitextract_8bkypk_n/
├── .eslintrc.js
├── .gitignore
├── .nvmrc
├── LICENSE.md
├── README.md
├── bower.json
├── contributing.md
├── imagesloaded.js
├── imagesloaded.pkgd.js
├── package.json
├── sandbox/
│ ├── background/
│ │ ├── css/
│ │ │ └── background.css
│ │ └── index.html
│ ├── picture.html
│ └── progress/
│ ├── index.html
│ └── progress.js
├── tasks/
│ ├── dist.js
│ └── version.js
└── test/
├── css/
│ └── tests.css
├── index.html
└── unit/
├── append.js
├── background.js
├── basics.js
├── data-uri.js
├── jquery-fail.js
├── jquery-success.js
├── local-files.js
├── no-images.js
├── non-element.js
├── picture.js
├── selector-string.js
├── single-element.js
└── srcset.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.js
================================================
/* eslint-env node */
module.exports = {
plugins: [ 'metafizzy' ],
extends: 'plugin:metafizzy/browser',
env: {
browser: true,
commonjs: true,
},
parserOptions: {
ecmaVersion: 2018,
},
globals: {
imagesLoaded: 'readonly',
QUnit: 'readonly',
},
rules: {
eqeqeq: [ 'error', 'smart' ],
'id-length': [ 'error', {
min: 2,
max: 30,
exceptions: [ 'x', 'y', 'z', 'i', 'j', 'a', 'b', 't', '$' ],
} ],
'new-cap': 'off',
},
ignorePatterns: [ '*pkgd*.js' ],
};
================================================
FILE: .gitignore
================================================
bower_components/
node_modules/
build/index.html
build/*.woff*
build/imagesloaded*.js
.netlify
================================================
FILE: .nvmrc
================================================
16
================================================
FILE: LICENSE.md
================================================
Copyright (c) 2011-2022 [David DeSandro](https://desandro.com) and [contributors](https://github.com/desandro/imagesloaded/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# imagesLoaded
JavaScript is all like "You images done yet or what?"
[imagesloaded.desandro.com](https://imagesloaded.desandro.com)
Detect when images have been loaded.
## Install
### Download
+ [imagesloaded.pkgd.min.js](https://unpkg.com/imagesloaded@5/imagesloaded.pkgd.min.js) minified
+ [imagesloaded.pkgd.js](https://unpkg.com/imagesloaded@5/imagesloaded.pkgd.js) un-minified
### CDN
``` html
```
### Package managers
Install via npm: `npm install imagesloaded`
Install via Yarn: `yarn add imagesloaded`
## jQuery
You can use imagesLoaded as a jQuery Plugin.
``` js
$('#container').imagesLoaded( function() {
// images have loaded
});
// options
$('#container').imagesLoaded( {
// options...
},
function() {
// images have loaded
}
);
```
`.imagesLoaded()` returns a [jQuery Deferred object](https://api.jquery.com/category/deferred-object/). This allows you to use `.always()`, `.done()`, `.fail()` and `.progress()`.
``` js
$('#container').imagesLoaded()
.always( function( instance ) {
console.log('all images loaded');
})
.done( function( instance ) {
console.log('all images successfully loaded');
})
.fail( function() {
console.log('all images loaded, at least one is broken');
})
.progress( function( instance, image ) {
var result = image.isLoaded ? 'loaded' : 'broken';
console.log( 'image is ' + result + ' for ' + image.img.src );
});
```
## Vanilla JavaScript
You can use imagesLoaded with vanilla JS.
``` js
imagesLoaded( elem, callback )
// options
imagesLoaded( elem, options, callback )
// you can use `new` if you like
new imagesLoaded( elem, callback )
```
+ `elem` _Element, NodeList, Array, or Selector String_
+ `options` _Object_
+ `callback` _Function_ - function triggered after all images have been loaded
Using a callback function is the same as binding it to the `always` event (see below).
``` js
// element
imagesLoaded( document.querySelector('#container'), function( instance ) {
console.log('all images are loaded');
});
// selector string
imagesLoaded( '#container', function() {...});
// multiple elements
var posts = document.querySelectorAll('.post');
imagesLoaded( posts, function() {...});
```
Bind events with vanilla JS with .on(), .off(), and .once() methods.
``` js
var imgLoad = imagesLoaded( elem );
function onAlways( instance ) {
console.log('all images are loaded');
}
// bind with .on()
imgLoad.on( 'always', onAlways );
// unbind with .off()
imgLoad.off( 'always', onAlways );
```
## Background
Detect when background images have loaded, in addition to ``s.
Set `{ background: true }` to detect when the element's background image has loaded.
``` js
// jQuery
$('#container').imagesLoaded( { background: true }, function() {
console.log('#container background image loaded');
});
// vanilla JS
imagesLoaded( '#container', { background: true }, function() {
console.log('#container background image loaded');
});
```
[See jQuery demo](https://codepen.io/desandro/pen/pjVMPB) or [vanilla JS demo](https://codepen.io/desandro/pen/avKooW) on CodePen.
Set to a selector string like `{ background: '.item' }` to detect when the background images of child elements have loaded.
``` js
// jQuery
$('#container').imagesLoaded( { background: '.item' }, function() {
console.log('all .item background images loaded');
});
// vanilla JS
imagesLoaded( '#container', { background: '.item' }, function() {
console.log('all .item background images loaded');
});
```
[See jQuery demo](https://codepen.io/desandro/pen/avKoZL) or [vanilla JS demo](https://codepen.io/desandro/pen/vNrBGz) on CodePen.
## Events
### always
``` js
// jQuery
$('#container').imagesLoaded().always( function( instance ) {
console.log('ALWAYS - all images have been loaded');
});
// vanilla JS
imgLoad.on( 'always', function( instance ) {
console.log('ALWAYS - all images have been loaded');
});
```
Triggered after all images have been either loaded or confirmed broken.
+ `instance` _imagesLoaded_ - the imagesLoaded instance
### done
``` js
// jQuery
$('#container').imagesLoaded().done( function( instance ) {
console.log('DONE - all images have been successfully loaded');
});
// vanilla JS
imgLoad.on( 'done', function( instance ) {
console.log('DONE - all images have been successfully loaded');
});
```
Triggered after all images have successfully loaded without any broken images.
### fail
``` js
$('#container').imagesLoaded().fail( function( instance ) {
console.log('FAIL - all images loaded, at least one is broken');
});
// vanilla JS
imgLoad.on( 'fail', function( instance ) {
console.log('FAIL - all images loaded, at least one is broken');
});
```
Triggered after all images have been loaded with at least one broken image.
### progress
``` js
imgLoad.on( 'progress', function( instance, image ) {
var result = image.isLoaded ? 'loaded' : 'broken';
console.log( 'image is ' + result + ' for ' + image.img.src );
});
```
Triggered after each image has been loaded.
+ `instance` _imagesLoaded_ - the imagesLoaded instance
+ `image` _LoadingImage_ - the LoadingImage instance of the loaded image
## Properties
### LoadingImage.img
_Image_ - The `img` element
### LoadingImage.isLoaded
_Boolean_ - `true` when the image has successfully loaded
### imagesLoaded.images
Array of _LoadingImage_ instances for each image detected
``` js
var imgLoad = imagesLoaded('#container');
imgLoad.on( 'always', function() {
console.log( imgLoad.images.length + ' images loaded' );
// detect which image is broken
for ( var i = 0, len = imgLoad.images.length; i < len; i++ ) {
var image = imgLoad.images[i];
var result = image.isLoaded ? 'loaded' : 'broken';
console.log( 'image is ' + result + ' for ' + image.img.src );
}
});
```
## Webpack
Install imagesLoaded with npm.
``` bash
npm install imagesloaded
```
You can then `require('imagesloaded')`.
``` js
// main.js
var imagesLoaded = require('imagesloaded');
imagesLoaded( '#container', function() {
// images have loaded
});
```
Use `.makeJQueryPlugin` to make `.imagesLoaded()` jQuery plugin.
``` js
// main.js
var imagesLoaded = require('imagesloaded');
var $ = require('jquery');
// provide jQuery argument
imagesLoaded.makeJQueryPlugin( $ );
// now use .imagesLoaded() jQuery plugin
$('#container').imagesLoaded( function() {...});
```
Run webpack.
``` bash
webpack main.js bundle.js
```
## Browserify
imagesLoaded works with [Browserify](https://browserify.org/).
``` bash
npm install imagesloaded --save
```
``` js
var imagesLoaded = require('imagesloaded');
imagesLoaded( elem, function() {...} );
```
Use `.makeJQueryPlugin` to make to use `.imagesLoaded()` jQuery plugin.
``` js
var $ = require('jquery');
var imagesLoaded = require('imagesloaded');
// provide jQuery argument
imagesLoaded.makeJQueryPlugin( $ );
// now use .imagesLoaded() jQuery plugin
$('#container').imagesLoaded( function() {...});
```
## Browser support
+ Chrome 49+
+ Firefox 41+
+ Edge 14+
+ iOS Safari 8+
Use [imagesLoaded v4](https://github.com/desandro/imagesloaded/tree/v4.1.4) for Internet Explorer and other older browser support.
## Development
Development uses Node.js v16 with npm v8
``` bash
nvm use
```
## MIT License
imagesLoaded is released under the [MIT License](https://desandro.mit-license.org/). Have at it.
================================================
FILE: bower.json
================================================
{
"name": "imagesloaded",
"description": "JavaScript is all like _You images done yet or what?_",
"main": "imagesloaded.js",
"dependencies": {
"ev-emitter": "^2.1.2"
},
"devDependencies": {
},
"ignore": [
"**/.*",
"test",
"package.json",
"composer.json",
"node_modules",
"bower_components",
"tests",
"sandbox/",
"gulpfile.js",
"contributing.md"
],
"homepage": "https://imagesloaded.desandro.com",
"authors": [
"David DeSandro"
],
"moduleType": [
"amd",
"globals",
"node"
],
"keywords": [
"images"
],
"license": "MIT"
}
================================================
FILE: contributing.md
================================================
## Submitting issues
### Reduced test case required
All bug reports and problem issues require a [**reduced test case**](https://css-tricks.com/reduced-test-cases/).
+ A reduced test case clearly demonstrates the bug or issue.
+ It contains the bare minimum HTML, CSS, and JavaScript required to demonstrate the bug.
+ A link to your production site is **not** a reduced test case.
Create a test case by forking a [CodePen demos](https://codepen.io/collection/xKRgYx).
+ [progress with jQuery](https://codepen.io/desandro/pen/podVjYW)
+ [progress with vanilla JS](https://codepen.io/desandro/pen/XWzqXrP)
+ [`{ background: true }` with jQuery](https://codepen.io/desandro/pen/QWOrNNY)
+ [`{ background: true }` with vanilla JS](https://codepen.io/desandro/pen/oNodxYW)
+ [`{ background: '.selector' }` with jQuery](https://codepen.io/desandro/pen/YzELWdo)
+ [`{ background: '.selector' }` with vanilla JS](https://codepen.io/desandro/pen/KKyRMEv)
Providing a reduced test case is the best way to get your issue addressed. They help you point out the problem. They help me verify and debug the problem. They help others understand the problem. Without a reduced test case, your issue may be closed.
================================================
FILE: imagesloaded.js
================================================
/*!
* imagesLoaded v5.0.0
* JavaScript is all like "You images are done yet or what?"
* MIT License
*/
( function( window, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( window, require('ev-emitter') );
} else {
// browser global
window.imagesLoaded = factory( window, window.EvEmitter );
}
} )( typeof window !== 'undefined' ? window : this,
function factory( window, EvEmitter ) {
let $ = window.jQuery;
let console = window.console;
// -------------------------- helpers -------------------------- //
// turn element or nodeList into an array
function makeArray( obj ) {
// use object if already an array
if ( Array.isArray( obj ) ) return obj;
let isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
// convert nodeList to array
if ( isArrayLike ) return [ ...obj ];
// array of single index
return [ obj ];
}
// -------------------------- imagesLoaded -------------------------- //
/**
* @param {[Array, Element, NodeList, String]} elem
* @param {[Object, Function]} options - if function, use as callback
* @param {Function} onAlways - callback function
* @returns {ImagesLoaded}
*/
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
let queryElem = elem;
if ( typeof elem == 'string' ) {
queryElem = document.querySelectorAll( elem );
}
// bail if bad element
if ( !queryElem ) {
console.error(`Bad element for imagesLoaded ${queryElem || elem}`);
return;
}
this.elements = makeArray( queryElem );
this.options = {};
// shift arguments if no options set
if ( typeof options == 'function' ) {
onAlways = options;
} else {
Object.assign( this.options, options );
}
if ( onAlways ) this.on( 'always', onAlways );
this.getImages();
// add jQuery Deferred object
if ( $ ) this.jqDeferred = new $.Deferred();
// HACK check async to allow time to bind listeners
setTimeout( this.check.bind( this ) );
}
ImagesLoaded.prototype = Object.create( EvEmitter.prototype );
ImagesLoaded.prototype.getImages = function() {
this.images = [];
// filter & find items if we have an item selector
this.elements.forEach( this.addElementImages, this );
};
const elementNodeTypes = [ 1, 9, 11 ];
/**
* @param {Node} elem
*/
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
let { nodeType } = elem;
if ( !nodeType || !elementNodeTypes.includes( nodeType ) ) return;
let childImgs = elem.querySelectorAll('img');
// concat childElems to filterFound array
for ( let img of childImgs ) {
this.addImage( img );
}
// get child background images
if ( typeof this.options.background == 'string' ) {
let children = elem.querySelectorAll( this.options.background );
for ( let child of children ) {
this.addElementBackgroundImages( child );
}
}
};
const reURL = /url\((['"])?(.*?)\1\)/gi;
ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
let style = getComputedStyle( elem );
// Firefox returns null if in a hidden iframe https://bugzil.la/548397
if ( !style ) return;
// get url inside url("...")
let matches = reURL.exec( style.backgroundImage );
while ( matches !== null ) {
let url = matches && matches[2];
if ( url ) {
this.addBackground( url, elem );
}
matches = reURL.exec( style.backgroundImage );
}
};
/**
* @param {Image} img
*/
ImagesLoaded.prototype.addImage = function( img ) {
let loadingImage = new LoadingImage( img );
this.images.push( loadingImage );
};
ImagesLoaded.prototype.addBackground = function( url, elem ) {
let background = new Background( url, elem );
this.images.push( background );
};
ImagesLoaded.prototype.check = function() {
this.progressedCount = 0;
this.hasAnyBroken = false;
// complete if no images
if ( !this.images.length ) {
this.complete();
return;
}
/* eslint-disable-next-line func-style */
let onProgress = ( image, elem, message ) => {
// HACK - Chrome triggers event before object properties have changed. #83
setTimeout( () => {
this.progress( image, elem, message );
} );
};
this.images.forEach( function( loadingImage ) {
loadingImage.once( 'progress', onProgress );
loadingImage.check();
} );
};
ImagesLoaded.prototype.progress = function( image, elem, message ) {
this.progressedCount++;
this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
// progress event
this.emitEvent( '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() {
let eventName = this.hasAnyBroken ? 'fail' : 'done';
this.isComplete = true;
this.emitEvent( eventName, [ this ] );
this.emitEvent( 'always', [ this ] );
if ( this.jqDeferred ) {
let jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
this.jqDeferred[ jqMethod ]( this );
}
};
// -------------------------- -------------------------- //
function LoadingImage( img ) {
this.img = img;
}
LoadingImage.prototype = Object.create( EvEmitter.prototype );
LoadingImage.prototype.check = function() {
// If complete is true and browser supports natural sizes,
// try to check for image status manually.
let 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();
// add crossOrigin attribute. #204
if ( this.img.crossOrigin ) {
this.proxyImage.crossOrigin = this.img.crossOrigin;
}
this.proxyImage.addEventListener( 'load', this );
this.proxyImage.addEventListener( 'error', this );
// bind to image as well for Firefox. #191
this.img.addEventListener( 'load', this );
this.img.addEventListener( 'error', this );
this.proxyImage.src = this.img.currentSrc || this.img.src;
};
LoadingImage.prototype.getIsImageComplete = function() {
// check for non-zero, non-undefined naturalWidth
// fixes Safari+InfiniteScroll+Masonry bug infinite-scroll#671
return this.img.complete && this.img.naturalWidth;
};
LoadingImage.prototype.confirm = function( isLoaded, message ) {
this.isLoaded = isLoaded;
let { parentNode } = this.img;
// emit progress with parent or self
let elem = parentNode.nodeName === 'PICTURE' ? parentNode : this.img;
this.emitEvent( 'progress', [ this, elem, message ] );
};
// ----- events ----- //
// trigger specified handler for event type
LoadingImage.prototype.handleEvent = function( event ) {
let 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() {
this.proxyImage.removeEventListener( 'load', this );
this.proxyImage.removeEventListener( 'error', this );
this.img.removeEventListener( 'load', this );
this.img.removeEventListener( 'error', this );
};
// -------------------------- Background -------------------------- //
function Background( url, element ) {
this.url = url;
this.element = element;
this.img = new Image();
}
// inherit LoadingImage prototype
Background.prototype = Object.create( LoadingImage.prototype );
Background.prototype.check = function() {
this.img.addEventListener( 'load', this );
this.img.addEventListener( 'error', this );
this.img.src = this.url;
// check if image is already complete
let isComplete = this.getIsImageComplete();
if ( isComplete ) {
this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
this.unbindEvents();
}
};
Background.prototype.unbindEvents = function() {
this.img.removeEventListener( 'load', this );
this.img.removeEventListener( 'error', this );
};
Background.prototype.confirm = function( isLoaded, message ) {
this.isLoaded = isLoaded;
this.emitEvent( '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, onAlways ) {
let instance = new ImagesLoaded( this, options, onAlways );
return instance.jqDeferred.promise( $( this ) );
};
};
// try making plugin
ImagesLoaded.makeJQueryPlugin();
// -------------------------- -------------------------- //
return ImagesLoaded;
} );
================================================
FILE: imagesloaded.pkgd.js
================================================
/*!
* imagesLoaded PACKAGED v5.0.0
* JavaScript is all like "You images are done yet or what?"
* MIT License
*/
/**
* EvEmitter v2.1.1
* Lil' event emitter
* MIT License
*/
( function( global, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS - Browserify, Webpack
module.exports = factory();
} else {
// Browser globals
global.EvEmitter = factory();
}
}( typeof window != 'undefined' ? window : this, function() {
function EvEmitter() {}
let proto = EvEmitter.prototype;
proto.on = function( eventName, listener ) {
if ( !eventName || !listener ) return this;
// set events hash
let events = this._events = this._events || {};
// set listeners array
let listeners = events[ eventName ] = events[ eventName ] || [];
// only add once
if ( !listeners.includes( listener ) ) {
listeners.push( listener );
}
return this;
};
proto.once = function( eventName, listener ) {
if ( !eventName || !listener ) return this;
// add event
this.on( eventName, listener );
// set once flag
// set onceEvents hash
let onceEvents = this._onceEvents = this._onceEvents || {};
// set onceListeners object
let onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
// set flag
onceListeners[ listener ] = true;
return this;
};
proto.off = function( eventName, listener ) {
let listeners = this._events && this._events[ eventName ];
if ( !listeners || !listeners.length ) return this;
let index = listeners.indexOf( listener );
if ( index != -1 ) {
listeners.splice( index, 1 );
}
return this;
};
proto.emitEvent = function( eventName, args ) {
let listeners = this._events && this._events[ eventName ];
if ( !listeners || !listeners.length ) return this;
// copy over to avoid interference if .off() in listener
listeners = listeners.slice( 0 );
args = args || [];
// once stuff
let onceListeners = this._onceEvents && this._onceEvents[ eventName ];
for ( let listener of listeners ) {
let isOnce = onceListeners && onceListeners[ listener ];
if ( isOnce ) {
// remove listener
// remove before trigger to prevent recursion
this.off( eventName, listener );
// unset once flag
delete onceListeners[ listener ];
}
// trigger listener
listener.apply( this, args );
}
return this;
};
proto.allOff = function() {
delete this._events;
delete this._onceEvents;
return this;
};
return EvEmitter;
} ) );
/*!
* imagesLoaded v5.0.0
* JavaScript is all like "You images are done yet or what?"
* MIT License
*/
( function( window, factory ) {
// universal module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( window, require('ev-emitter') );
} else {
// browser global
window.imagesLoaded = factory( window, window.EvEmitter );
}
} )( typeof window !== 'undefined' ? window : this,
function factory( window, EvEmitter ) {
let $ = window.jQuery;
let console = window.console;
// -------------------------- helpers -------------------------- //
// turn element or nodeList into an array
function makeArray( obj ) {
// use object if already an array
if ( Array.isArray( obj ) ) return obj;
let isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
// convert nodeList to array
if ( isArrayLike ) return [ ...obj ];
// array of single index
return [ obj ];
}
// -------------------------- imagesLoaded -------------------------- //
/**
* @param {[Array, Element, NodeList, String]} elem
* @param {[Object, Function]} options - if function, use as callback
* @param {Function} onAlways - callback function
* @returns {ImagesLoaded}
*/
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
let queryElem = elem;
if ( typeof elem == 'string' ) {
queryElem = document.querySelectorAll( elem );
}
// bail if bad element
if ( !queryElem ) {
console.error(`Bad element for imagesLoaded ${queryElem || elem}`);
return;
}
this.elements = makeArray( queryElem );
this.options = {};
// shift arguments if no options set
if ( typeof options == 'function' ) {
onAlways = options;
} else {
Object.assign( this.options, options );
}
if ( onAlways ) this.on( 'always', onAlways );
this.getImages();
// add jQuery Deferred object
if ( $ ) this.jqDeferred = new $.Deferred();
// HACK check async to allow time to bind listeners
setTimeout( this.check.bind( this ) );
}
ImagesLoaded.prototype = Object.create( EvEmitter.prototype );
ImagesLoaded.prototype.getImages = function() {
this.images = [];
// filter & find items if we have an item selector
this.elements.forEach( this.addElementImages, this );
};
const elementNodeTypes = [ 1, 9, 11 ];
/**
* @param {Node} elem
*/
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
let { nodeType } = elem;
if ( !nodeType || !elementNodeTypes.includes( nodeType ) ) return;
let childImgs = elem.querySelectorAll('img');
// concat childElems to filterFound array
for ( let img of childImgs ) {
this.addImage( img );
}
// get child background images
if ( typeof this.options.background == 'string' ) {
let children = elem.querySelectorAll( this.options.background );
for ( let child of children ) {
this.addElementBackgroundImages( child );
}
}
};
const reURL = /url\((['"])?(.*?)\1\)/gi;
ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
let style = getComputedStyle( elem );
// Firefox returns null if in a hidden iframe https://bugzil.la/548397
if ( !style ) return;
// get url inside url("...")
let matches = reURL.exec( style.backgroundImage );
while ( matches !== null ) {
let url = matches && matches[2];
if ( url ) {
this.addBackground( url, elem );
}
matches = reURL.exec( style.backgroundImage );
}
};
/**
* @param {Image} img
*/
ImagesLoaded.prototype.addImage = function( img ) {
let loadingImage = new LoadingImage( img );
this.images.push( loadingImage );
};
ImagesLoaded.prototype.addBackground = function( url, elem ) {
let background = new Background( url, elem );
this.images.push( background );
};
ImagesLoaded.prototype.check = function() {
this.progressedCount = 0;
this.hasAnyBroken = false;
// complete if no images
if ( !this.images.length ) {
this.complete();
return;
}
/* eslint-disable-next-line func-style */
let onProgress = ( image, elem, message ) => {
// HACK - Chrome triggers event before object properties have changed. #83
setTimeout( () => {
this.progress( image, elem, message );
} );
};
this.images.forEach( function( loadingImage ) {
loadingImage.once( 'progress', onProgress );
loadingImage.check();
} );
};
ImagesLoaded.prototype.progress = function( image, elem, message ) {
this.progressedCount++;
this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
// progress event
this.emitEvent( '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() {
let eventName = this.hasAnyBroken ? 'fail' : 'done';
this.isComplete = true;
this.emitEvent( eventName, [ this ] );
this.emitEvent( 'always', [ this ] );
if ( this.jqDeferred ) {
let jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
this.jqDeferred[ jqMethod ]( this );
}
};
// -------------------------- -------------------------- //
function LoadingImage( img ) {
this.img = img;
}
LoadingImage.prototype = Object.create( EvEmitter.prototype );
LoadingImage.prototype.check = function() {
// If complete is true and browser supports natural sizes,
// try to check for image status manually.
let 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();
// add crossOrigin attribute. #204
if ( this.img.crossOrigin ) {
this.proxyImage.crossOrigin = this.img.crossOrigin;
}
this.proxyImage.addEventListener( 'load', this );
this.proxyImage.addEventListener( 'error', this );
// bind to image as well for Firefox. #191
this.img.addEventListener( 'load', this );
this.img.addEventListener( 'error', this );
this.proxyImage.src = this.img.currentSrc || this.img.src;
};
LoadingImage.prototype.getIsImageComplete = function() {
// check for non-zero, non-undefined naturalWidth
// fixes Safari+InfiniteScroll+Masonry bug infinite-scroll#671
return this.img.complete && this.img.naturalWidth;
};
LoadingImage.prototype.confirm = function( isLoaded, message ) {
this.isLoaded = isLoaded;
let { parentNode } = this.img;
// emit progress with parent or self
let elem = parentNode.nodeName === 'PICTURE' ? parentNode : this.img;
this.emitEvent( 'progress', [ this, elem, message ] );
};
// ----- events ----- //
// trigger specified handler for event type
LoadingImage.prototype.handleEvent = function( event ) {
let 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() {
this.proxyImage.removeEventListener( 'load', this );
this.proxyImage.removeEventListener( 'error', this );
this.img.removeEventListener( 'load', this );
this.img.removeEventListener( 'error', this );
};
// -------------------------- Background -------------------------- //
function Background( url, element ) {
this.url = url;
this.element = element;
this.img = new Image();
}
// inherit LoadingImage prototype
Background.prototype = Object.create( LoadingImage.prototype );
Background.prototype.check = function() {
this.img.addEventListener( 'load', this );
this.img.addEventListener( 'error', this );
this.img.src = this.url;
// check if image is already complete
let isComplete = this.getIsImageComplete();
if ( isComplete ) {
this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
this.unbindEvents();
}
};
Background.prototype.unbindEvents = function() {
this.img.removeEventListener( 'load', this );
this.img.removeEventListener( 'error', this );
};
Background.prototype.confirm = function( isLoaded, message ) {
this.isLoaded = isLoaded;
this.emitEvent( '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, onAlways ) {
let instance = new ImagesLoaded( this, options, onAlways );
return instance.jqDeferred.promise( $( this ) );
};
};
// try making plugin
ImagesLoaded.makeJQueryPlugin();
// -------------------------- -------------------------- //
return ImagesLoaded;
} );
================================================
FILE: package.json
================================================
{
"name": "imagesloaded",
"version": "5.0.0",
"description": "JavaScript is all like _You images done yet or what?_",
"main": "imagesloaded.js",
"files": [
"imagesloaded*.js"
],
"dependencies": {
"ev-emitter": "^2.1.2"
},
"devDependencies": {
"eslint": "^7.32.0",
"eslint-plugin-metafizzy": "^1.2.1",
"jquery": "^3.6.0",
"qunit": "^2.17.2",
"terser": "^5.10.0"
},
"scripts": {
"test": "npm run lint",
"lint": "npx eslint .",
"dist": "node tasks/dist.js",
"version": "node tasks/version.js && npm run dist && git add -A ."
},
"repository": {
"type": "git",
"url": "git://github.com/desandro/imagesloaded.git"
},
"keywords": [
"images",
"loaded",
"ui",
"dom",
"jquery-plugin"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/desandro/imagesloaded/issues"
},
"homepage": "https://github.com/desandro/imagesloaded",
"directories": {
"test": "test"
},
"author": "David DeSandro"
}
================================================
FILE: sandbox/background/css/background.css
================================================
.box {
width: 300px;
height: 300px;
margin: 0 20px 20px 0;
border: 1px solid;
display: inline-block;
}
.orange-tree {
background: url('https://i.imgur.com/bwy74ok.jpg');
background-size: cover;
}
.thunder-cloud {
background: url('../../../test/img/thunder-cloud.jpg');
background-size: contain;
}
.multi1 {
background:
url("https://i.imgur.com/ZAVN3.png"),
url('https://i.imgur.com/6UdOxeB.png') bottom right,
url(https://i.imgur.com/LkmcILl.jpg);
background-size: cover;
}
.blue {
background: #09F;
}
================================================
FILE: sandbox/background/index.html
================================================
background