]>
Commit | Line | Data |
---|---|---|
1 | /*! | |
2 | hey, [be]Lazy.js - v1.3.1 - 2015.02.01 | |
3 | A lazy loading and multi-serving image script | |
4 | (c) Bjoern Klinggaard - @bklinggaard - http://dinbror.dk/blazy | |
5 | */ | |
6 | ;(function(root, blazy) { | |
7 | if (typeof define === 'function' && define.amd) { | |
8 | // AMD. Register bLazy as an anonymous module | |
9 | define(blazy); | |
10 | } else if (typeof exports === 'object') { | |
11 | // Node. Does not work with strict CommonJS, but | |
12 | // only CommonJS-like environments that support module.exports, | |
13 | // like Node. | |
14 | module.exports = blazy(); | |
15 | } else { | |
16 | // Browser globals. Register bLazy on window | |
17 | root.Blazy = blazy(); | |
18 | } | |
19 | })(this, function () { | |
20 | 'use strict'; | |
21 | ||
22 | //vars | |
23 | var source, options, viewport, images, count, isRetina, destroyed; | |
24 | //throttle vars | |
25 | var validateT, saveViewportOffsetT; | |
26 | ||
27 | // constructor | |
28 | function Blazy(settings) { | |
29 | //IE7- fallback for missing querySelectorAll support | |
30 | if (!document.querySelectorAll) { | |
31 | var s=document.createStyleSheet(); | |
32 | document.querySelectorAll = function(r, c, i, j, a) { | |
33 | a=document.all, c=[], r = r.replace(/\[for\b/gi, '[htmlFor').split(','); | |
34 | for (i=r.length; i--;) { | |
35 | s.addRule(r[i], 'k:v'); | |
36 | for (j=a.length; j--;) a[j].currentStyle.k && c.push(a[j]); | |
37 | s.removeRule(0); | |
38 | } | |
39 | return c; | |
40 | }; | |
41 | } | |
42 | //init vars | |
43 | destroyed = true; | |
44 | images = []; | |
45 | viewport = {}; | |
46 | //options | |
47 | options = settings || {}; | |
48 | options.error = options.error || false; | |
49 | options.offset = options.offset || 100; | |
50 | options.success = options.success || false; | |
51 | options.selector = options.selector || '.b-lazy'; | |
52 | options.separator = options.separator || '|'; | |
53 | options.container = options.container ? document.querySelectorAll(options.container) : false; | |
54 | options.errorClass = options.errorClass || 'b-error'; | |
55 | options.breakpoints = options.breakpoints || false; | |
56 | options.successClass = options.successClass || 'b-loaded'; | |
57 | options.src = source = options.src || 'data-src'; | |
58 | isRetina = window.devicePixelRatio > 1; | |
59 | viewport.top = 0 - options.offset; | |
60 | viewport.left = 0 - options.offset; | |
61 | //throttle, ensures that we don't call the functions too often | |
62 | validateT = throttle(validate, 25); | |
63 | saveViewportOffsetT = throttle(saveViewportOffset, 50); | |
64 | ||
65 | saveViewportOffset(); | |
66 | ||
67 | //handle multi-served image src | |
68 | each(options.breakpoints, function(object){ | |
69 | if(object.width >= window.screen.width) { | |
70 | source = object.src; | |
71 | return false; | |
72 | } | |
73 | }); | |
74 | ||
75 | // start lazy load | |
76 | initialize(); | |
77 | } | |
78 | ||
79 | /* public functions | |
80 | ************************************/ | |
81 | Blazy.prototype.revalidate = function() { | |
82 | initialize(); | |
83 | }; | |
84 | Blazy.prototype.load = function(element, force){ | |
85 | if(!isElementLoaded(element)) loadImage(element, force); | |
86 | }; | |
87 | Blazy.prototype.destroy = function(){ | |
88 | if(options.container){ | |
89 | each(options.container, function(object){ | |
90 | unbindEvent(object, 'scroll', validateT); | |
91 | }); | |
92 | } | |
93 | unbindEvent(window, 'scroll', validateT); | |
94 | unbindEvent(window, 'resize', validateT); | |
95 | unbindEvent(window, 'resize', saveViewportOffsetT); | |
96 | count = 0; | |
97 | images.length = 0; | |
98 | destroyed = true; | |
99 | }; | |
100 | ||
101 | /* private helper functions | |
102 | ************************************/ | |
103 | function initialize(){ | |
104 | // First we create an array of images to lazy load | |
105 | createImageArray(options.selector); | |
106 | // Then we bind resize and scroll events if not already binded | |
107 | if(destroyed) { | |
108 | destroyed = false; | |
109 | if(options.container) { | |
110 | each(options.container, function(object){ | |
111 | bindEvent(object, 'scroll', validateT); | |
112 | }); | |
113 | } | |
114 | bindEvent(window, 'resize', saveViewportOffsetT); | |
115 | bindEvent(window, 'resize', validateT); | |
116 | bindEvent(window, 'scroll', validateT); | |
117 | } | |
118 | // And finally, we start to lazy load. Should bLazy ensure domready? | |
119 | validate(); | |
120 | } | |
121 | ||
122 | function validate() { | |
123 | for(var i = 0; i<count; i++){ | |
124 | var image = images[i]; | |
125 | if(elementInView(image) || isElementLoaded(image)) { | |
126 | Blazy.prototype.load(image); | |
127 | images.splice(i, 1); | |
128 | count--; | |
129 | i--; | |
130 | } | |
131 | } | |
132 | if(count === 0) { | |
133 | Blazy.prototype.destroy(); | |
134 | } | |
135 | } | |
136 | ||
137 | function loadImage(ele, force){ | |
138 | // if element is visible | |
139 | if(force || (ele.offsetWidth > 0 && ele.offsetHeight > 0)) { | |
140 | var dataSrc = ele.getAttribute(source) || ele.getAttribute(options.src); // fallback to default data-src | |
141 | if(dataSrc) { | |
142 | var dataSrcSplitted = dataSrc.split(options.separator); | |
143 | var src = dataSrcSplitted[isRetina && dataSrcSplitted.length > 1 ? 1 : 0]; | |
144 | var img = new Image(); | |
145 | // cleanup markup, remove data source attributes | |
146 | each(options.breakpoints, function(object){ | |
147 | ele.removeAttribute(object.src); | |
148 | }); | |
149 | ele.removeAttribute(options.src); | |
150 | img.onerror = function() { | |
151 | if(options.error) options.error(ele, "invalid"); | |
152 | ele.className = ele.className + ' ' + options.errorClass; | |
153 | }; | |
154 | img.onload = function() { | |
155 | // Is element an image or should we add the src as a background image? | |
156 | ele.nodeName.toLowerCase() === 'img' ? ele.src = src : ele.style.backgroundImage = 'url("' + src + '")'; | |
157 | ele.className = ele.className + ' ' + options.successClass; | |
158 | if(options.success) options.success(ele); | |
159 | }; | |
160 | img.src = src; //preload image | |
161 | } else { | |
162 | if(options.error) options.error(ele, "missing"); | |
163 | ele.className = ele.className + ' ' + options.errorClass; | |
164 | } | |
165 | } | |
166 | } | |
167 | ||
168 | function elementInView(ele) { | |
169 | var rect = ele.getBoundingClientRect(); | |
170 | ||
171 | return ( | |
172 | // Intersection | |
173 | rect.right >= viewport.left | |
174 | && rect.bottom >= viewport.top | |
175 | && rect.left <= viewport.right | |
176 | && rect.top <= viewport.bottom | |
177 | ); | |
178 | } | |
179 | ||
180 | function isElementLoaded(ele) { | |
181 | return (' ' + ele.className + ' ').indexOf(' ' + options.successClass + ' ') !== -1; | |
182 | } | |
183 | ||
184 | function createImageArray(selector) { | |
185 | var nodelist = document.querySelectorAll(selector); | |
186 | count = nodelist.length; | |
187 | //converting nodelist to array | |
188 | for(var i = count; i--; images.unshift(nodelist[i])){} | |
189 | } | |
190 | ||
191 | function saveViewportOffset(){ | |
192 | viewport.bottom = (window.innerHeight || document.documentElement.clientHeight) + options.offset; | |
193 | viewport.right = (window.innerWidth || document.documentElement.clientWidth) + options.offset; | |
194 | } | |
195 | ||
196 | function bindEvent(ele, type, fn) { | |
197 | if (ele.attachEvent) { | |
198 | ele.attachEvent && ele.attachEvent('on' + type, fn); | |
199 | } else { | |
200 | ele.addEventListener(type, fn, false); | |
201 | } | |
202 | } | |
203 | ||
204 | function unbindEvent(ele, type, fn) { | |
205 | if (ele.detachEvent) { | |
206 | ele.detachEvent && ele.detachEvent('on' + type, fn); | |
207 | } else { | |
208 | ele.removeEventListener(type, fn, false); | |
209 | } | |
210 | } | |
211 | ||
212 | function each(object, fn){ | |
213 | if(object && fn) { | |
214 | var l = object.length; | |
215 | for(var i = 0; i<l && fn(object[i], i) !== false; i++){} | |
216 | } | |
217 | } | |
218 | ||
219 | function throttle(fn, minDelay) { | |
220 | var lastCall = 0; | |
221 | return function() { | |
222 | var now = +new Date(); | |
223 | if (now - lastCall < minDelay) { | |
224 | return; | |
225 | } | |
226 | lastCall = now; | |
227 | fn.apply(images, arguments); | |
228 | }; | |
229 | } | |
230 | ||
231 | return Blazy; | |
232 | }); |