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