| 1 |
/*! waitForImages jQuery Plugin - v2.0.2 - 2015-06-02 |
| 2 |
* https://github.com/alexanderdickson/waitForImages |
| 3 |
* Copyright (c) 2015 Alex Dickson; Licensed MIT */ |
| 4 |
|
| 5 |
;(function (factory) { |
| 6 |
if (typeof define === 'function' && define.amd) { |
| 7 |
// AMD. Register as an anonymous module. |
| 8 |
define(['jquery'], factory); |
| 9 |
} else if (typeof exports === 'object') { |
| 10 |
// CommonJS / nodejs module |
| 11 |
module.exports = factory(require('jquery')); |
| 12 |
} else { |
| 13 |
// Browser globals |
| 14 |
factory(jQuery); |
| 15 |
} |
| 16 |
}(function ($) { |
| 17 |
// Namespace all events. |
| 18 |
var eventNamespace = 'waitForImages'; |
| 19 |
|
| 20 |
// CSS properties which contain references to images. |
| 21 |
$.waitForImages = { |
| 22 |
hasImageProperties: [ |
| 23 |
'backgroundImage', |
| 24 |
'listStyleImage', |
| 25 |
'borderImage', |
| 26 |
'borderCornerImage', |
| 27 |
'cursor' |
| 28 |
], |
| 29 |
hasImageAttributes: ['srcset'] |
| 30 |
}; |
| 31 |
|
| 32 |
// Custom selector to find all `img` elements with a valid `src` attribute. |
| 33 |
$.expr[':']['has-src'] = function (obj) { |
| 34 |
// Ensure we are dealing with an `img` element with a valid |
| 35 |
// `src` attribute. |
| 36 |
return $(obj).is('img[src][src!=""]'); |
| 37 |
}; |
| 38 |
|
| 39 |
// Custom selector to find images which are not already cached by the |
| 40 |
// browser. |
| 41 |
$.expr[':'].uncached = function (obj) { |
| 42 |
// Ensure we are dealing with an `img` element with a valid |
| 43 |
// `src` attribute. |
| 44 |
if (!$(obj).is(':has-src')) { |
| 45 |
return false; |
| 46 |
} |
| 47 |
|
| 48 |
return !obj.complete; |
| 49 |
}; |
| 50 |
|
| 51 |
$.fn.waitForImages = function () { |
| 52 |
|
| 53 |
var allImgsLength = 0; |
| 54 |
var allImgsLoaded = 0; |
| 55 |
var deferred = $.Deferred(); |
| 56 |
|
| 57 |
var finishedCallback; |
| 58 |
var eachCallback; |
| 59 |
var waitForAll; |
| 60 |
|
| 61 |
// Handle options object (if passed). |
| 62 |
if ($.isPlainObject(arguments[0])) { |
| 63 |
|
| 64 |
waitForAll = arguments[0].waitForAll; |
| 65 |
eachCallback = arguments[0].each; |
| 66 |
finishedCallback = arguments[0].finished; |
| 67 |
|
| 68 |
} else { |
| 69 |
|
| 70 |
// Handle if using deferred object and only one param was passed in. |
| 71 |
if (arguments.length === 1 && $.type(arguments[0]) === 'boolean') { |
| 72 |
waitForAll = arguments[0]; |
| 73 |
} else { |
| 74 |
finishedCallback = arguments[0]; |
| 75 |
eachCallback = arguments[1]; |
| 76 |
waitForAll = arguments[2]; |
| 77 |
} |
| 78 |
|
| 79 |
} |
| 80 |
|
| 81 |
// Handle missing callbacks. |
| 82 |
finishedCallback = finishedCallback || $.noop; |
| 83 |
eachCallback = eachCallback || $.noop; |
| 84 |
|
| 85 |
// Convert waitForAll to Boolean |
| 86 |
waitForAll = !! waitForAll; |
| 87 |
|
| 88 |
// Ensure callbacks are functions. |
| 89 |
if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) { |
| 90 |
throw new TypeError('An invalid callback was supplied.'); |
| 91 |
} |
| 92 |
|
| 93 |
this.each(function () { |
| 94 |
// Build a list of all imgs, dependent on what images will |
| 95 |
// be considered. |
| 96 |
var obj = $(this); |
| 97 |
var allImgs = []; |
| 98 |
// CSS properties which may contain an image. |
| 99 |
var hasImgProperties = $.waitForImages.hasImageProperties || []; |
| 100 |
// Element attributes which may contain an image. |
| 101 |
var hasImageAttributes = $.waitForImages.hasImageAttributes || []; |
| 102 |
// To match `url()` references. |
| 103 |
// Spec: http://www.w3.org/TR/CSS2/syndata.html#value-def-uri |
| 104 |
var matchUrl = /url\(\s*(['"]?)(.*?)\1\s*\)/g; |
| 105 |
|
| 106 |
if (waitForAll) { |
| 107 |
|
| 108 |
// Get all elements (including the original), as any one of |
| 109 |
// them could have a background image. |
| 110 |
obj.find('*').addBack().each(function () { |
| 111 |
var element = $(this); |
| 112 |
|
| 113 |
// If an `img` element, add it. But keep iterating in |
| 114 |
// case it has a background image too. |
| 115 |
if (element.is('img:has-src')) { |
| 116 |
allImgs.push({ |
| 117 |
src: element.attr('src'), |
| 118 |
element: element[0] |
| 119 |
}); |
| 120 |
} |
| 121 |
|
| 122 |
$.each(hasImgProperties, function (i, property) { |
| 123 |
var propertyValue = element.css(property); |
| 124 |
var match; |
| 125 |
|
| 126 |
// If it doesn't contain this property, skip. |
| 127 |
if (!propertyValue) { |
| 128 |
return true; |
| 129 |
} |
| 130 |
|
| 131 |
// Get all url() of this element. |
| 132 |
while (match = matchUrl.exec(propertyValue)) { |
| 133 |
allImgs.push({ |
| 134 |
src: match[2], |
| 135 |
element: element[0] |
| 136 |
}); |
| 137 |
} |
| 138 |
}); |
| 139 |
|
| 140 |
$.each(hasImageAttributes, function (i, attribute) { |
| 141 |
var attributeValue = element.attr(attribute); |
| 142 |
var attributeValues; |
| 143 |
|
| 144 |
// If it doesn't contain this property, skip. |
| 145 |
if (!attributeValue) { |
| 146 |
return true; |
| 147 |
} |
| 148 |
|
| 149 |
// Check for multiple comma separated images |
| 150 |
attributeValues = attributeValue.split(','); |
| 151 |
|
| 152 |
$.each(attributeValues, function(i, value) { |
| 153 |
// Trim value and get string before first |
| 154 |
// whitespace (for use with srcset). |
| 155 |
value = $.trim(value).split(' ')[0]; |
| 156 |
allImgs.push({ |
| 157 |
src: value, |
| 158 |
element: element[0] |
| 159 |
}); |
| 160 |
}); |
| 161 |
}); |
| 162 |
}); |
| 163 |
} else { |
| 164 |
// For images only, the task is simpler. |
| 165 |
obj.find('img:has-src') |
| 166 |
.each(function () { |
| 167 |
allImgs.push({ |
| 168 |
src: this.src, |
| 169 |
element: this |
| 170 |
}); |
| 171 |
}); |
| 172 |
} |
| 173 |
|
| 174 |
allImgsLength = allImgs.length; |
| 175 |
allImgsLoaded = 0; |
| 176 |
|
| 177 |
// If no images found, don't bother. |
| 178 |
if (allImgsLength === 0) { |
| 179 |
finishedCallback.call(obj[0]); |
| 180 |
deferred.resolveWith(obj[0]); |
| 181 |
} |
| 182 |
|
| 183 |
$.each(allImgs, function (i, img) { |
| 184 |
|
| 185 |
var image = new Image(); |
| 186 |
var events = |
| 187 |
'load.' + eventNamespace + ' error.' + eventNamespace; |
| 188 |
|
| 189 |
// Handle the image loading and error with the same callback. |
| 190 |
$(image).one(events, function me (event) { |
| 191 |
// If an error occurred with loading the image, set the |
| 192 |
// third argument accordingly. |
| 193 |
var eachArguments = [ |
| 194 |
allImgsLoaded, |
| 195 |
allImgsLength, |
| 196 |
event.type == 'load' |
| 197 |
]; |
| 198 |
allImgsLoaded++; |
| 199 |
|
| 200 |
eachCallback.apply(img.element, eachArguments); |
| 201 |
deferred.notifyWith(img.element, eachArguments); |
| 202 |
|
| 203 |
// Unbind the event listeners. I use this in addition to |
| 204 |
// `one` as one of those events won't be called (either |
| 205 |
// 'load' or 'error' will be called). |
| 206 |
$(this).off(events, me); |
| 207 |
|
| 208 |
if (allImgsLoaded == allImgsLength) { |
| 209 |
finishedCallback.call(obj[0]); |
| 210 |
deferred.resolveWith(obj[0]); |
| 211 |
return false; |
| 212 |
} |
| 213 |
|
| 214 |
}); |
| 215 |
|
| 216 |
image.src = img.src; |
| 217 |
}); |
| 218 |
}); |
| 219 |
|
| 220 |
return deferred.promise(); |
| 221 |
}; |
| 222 |
})); |
| 223 |
|
| 224 |
|