/*! waitForImages jQuery Plugin - v2.4.0 - 2018-02-13 * https://github.com/alexanderdickson/waitForImages * Copyright (c) 2018 Alex Dickson; Licensed MIT */ ;(function (factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['jquery'], factory); } else if (typeof exports === 'object') { // CommonJS / nodejs module module.exports = factory(require('jquery')); } else { // Browser globals factory(jQuery); } }(function ($) { // Namespace all events. var eventNamespace = 'waitForImages'; // Is srcset supported by this browser? var hasSrcset = (function(img) { return img.srcset && img.sizes; })(new Image()); // CSS properties which contain references to images. $.waitForImages = { hasImageProperties: [ 'backgroundImage', 'listStyleImage', 'borderImage', 'borderCornerImage', 'cursor' ], hasImageAttributes: ['srcset'] }; // Custom selector to find all `img` elements with a valid `src` attribute. $.expr.pseudos['has-src'] = function (obj) { // Ensure we are dealing with an `img` element with a valid // `src` attribute. return $(obj).is('img[src][src!=""]'); }; // Custom selector to find images which are not already cached by the // browser. $.expr.pseudos.uncached = function (obj) { // Ensure we are dealing with an `img` element with a valid // `src` attribute. if (!$(obj).is(':has-src')) { return false; } return !obj.complete; }; $.fn.waitForImages = function () { var allImgsLength = 0; var allImgsLoaded = 0; var deferred = $.Deferred(); var originalCollection = this; var allImgs = []; // CSS properties which may contain an image. var hasImgProperties = $.waitForImages.hasImageProperties || []; // Element attributes which may contain an image. var hasImageAttributes = $.waitForImages.hasImageAttributes || []; // To match `url()` references. // Spec: http://www.w3.org/TR/CSS2/syndata.html#value-def-uri var matchUrl = /url\(\s*(['"]?)(.*?)\1\s*\)/g; var finishedCallback; var eachCallback; var waitForAll; // Handle options object (if passed). if ($.isPlainObject(arguments[0])) { waitForAll = arguments[0].waitForAll; eachCallback = arguments[0].each; finishedCallback = arguments[0].finished; } else { // Handle if using deferred object and only one param was passed in. if (arguments.length === 1 && $.type(arguments[0]) === 'boolean') { waitForAll = arguments[0]; } else { finishedCallback = arguments[0]; eachCallback = arguments[1]; waitForAll = arguments[2]; } } // Handle missing callbacks. finishedCallback = finishedCallback || $.noop; eachCallback = eachCallback || $.noop; // Convert waitForAll to Boolean. waitForAll = !! waitForAll; // Ensure callbacks are functions. if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) { throw new TypeError('An invalid callback was supplied.'); } this.each(function () { // Build a list of all imgs, dependent on what images will // be considered. var obj = $(this); if (waitForAll) { // Get all elements (including the original), as any one of // them could have a background image. obj.find('*').addBack().each(function () { var element = $(this); // If an `img` element, add it. But keep iterating in // case it has a background image too. if (element.is('img:has-src') && !element.is('[srcset]')) { allImgs.push({ src: element.attr('src'), element: element[0] }); } $.each(hasImgProperties, function (i, property) { var propertyValue = element.css(property); var match; // If it doesn't contain this property, skip. if (!propertyValue) { return true; } // Get all url() of this element. while (match = matchUrl.exec(propertyValue)) { allImgs.push({ src: match[2], element: element[0] }); } }); $.each(hasImageAttributes, function (i, attribute) { var attributeValue = element.attr(attribute); var attributeValues; // If it doesn't contain this property, skip. if (!attributeValue) { return true; } allImgs.push({ src: element.attr('src'), srcset: element.attr('srcset'), element: element[0] }); }); }); } else { // For images only, the task is simpler. obj.find('img:has-src') .each(function () { allImgs.push({ src: this.src, element: this }); }); } }); allImgsLength = allImgs.length; allImgsLoaded = 0; // If no images found, don't bother. if (allImgsLength === 0) { finishedCallback.call(originalCollection); deferred.resolveWith(originalCollection); } // Now that we've found all imgs in all elements in this, // load them and attach callbacks. $.each(allImgs, function (i, img) { var image = new Image(); var events = 'load.' + eventNamespace + ' error.' + eventNamespace; // Handle the image loading and error with the same callback. $(image).one(events, function me (event) { // If an error occurred with loading the image, set the // third argument accordingly. var eachArguments = [ allImgsLoaded, allImgsLength, event.type == 'load' ]; allImgsLoaded++; eachCallback.apply(img.element, eachArguments); deferred.notifyWith(img.element, eachArguments); // Unbind the event listeners. I use this in addition to // `one` as one of those events won't be called (either // 'load' or 'error' will be called). $(this).off(events, me); if (allImgsLoaded == allImgsLength) { finishedCallback.call(originalCollection[0]); deferred.resolveWith(originalCollection[0]); return false; } }); if (hasSrcset && img.srcset) { image.srcset = img.srcset; image.sizes = img.sizes; } image.src = img.src; }); return deferred.promise(); }; }));