Project

General

Profile

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
}));
(14-14/27)