1/** 2 * @preserve LaziestLoader - v0.7.3 - 2016-01-03 3 * A responsive lazy loader for jQuery. 4 * http://sjwilliams.github.io/laziestloader/ 5 * Copyright (c) 2016 Josh Williams; Licensed MIT 6 */ 7 8 (function(factory) { 9 if (typeof define === 'function' && define.amd) { 10 define(['jquery'], factory); 11 } else { 12 factory(jQuery); 13 } 14}(function($) { 15 16 var laziestLoader = function(options, callback) { 17 18 var $w = $(window), 19 $elements = this, 20 $loaded = $(), // elements with the correct source set 21 retina = window.devicePixelRatio > 1, 22 didScroll = false; 23 24 options = $.extend(true, { 25 threshold: 0, 26 sizePattern: /{{SIZE}}/ig, 27 getSource: getSource, 28 event: 'scroll', 29 scrollThrottle: 250, // time in ms to throttle scroll. Increase for better performance. 30 sizeOffsetPercent: 0, // Subtract this % of width from the containing element, and fit images as though the element were that size. 0 = fit to element size; 40 = size - 40%; -30 = size + 30% 31 setSourceMode: true // plugin sets source attribute of the element. Set to false if you would like to, instead, use the callback to completely manage the element on trigger. 32 }, options); 33 34 35 var useNativeScroll = (typeof options.event === 'string') && (options.event.indexOf('scroll') === 0); 36 37 /** 38 * Generate source path of image to load. Take into account 39 * type of data supplied and whether or not a retina 40 * image is available. 41 * 42 * Basic option: data attributes specifing a single image to load, 43 * regardless of viewport. 44 * Eg: 45 * 46 * <img data-src="yourimage.jpg"> 47 * <img data-src="yourimage.jpg" data-src-retina="yourretinaimage.jpg"> 48 * 49 * Range of sizes: specify a string path with a {{size}} that 50 * will be replaced by an integer from a list of available sizes. 51 * Eg: 52 * 53 * <img data-pattern="path/toyourimage-{{size}}.jpg" data-widths="[320, 640, 970]"> 54 * <img data-pattern="path/toyourimage-{{size}}.jpg" data-pattern-retina="path/toyourimage-{{size}}@2x.jpg" data-widths="[320, 640, 970]"> 55 * <img data-pattern="path/toyourimage/{{size}}/slug.jpg" data-pattern-retina="path/toyourimage/{{size}}/[email protected]" data-widths="[320, 640, 970]"> 56 * 57 * Range of sizes, with slugs: specify a string path with a {{size}} that 58 * will be replaced by a slug representing an image size. 59 * Eg: 60 * 61 * <img data-pattern="path/toyourimage-{{size}}.jpg" data-widths="[{width: 320, slug: 'small'},{width:900, slug: 'large'}]"> 62 * 63 * @param {jQuery object} $el 64 * @return {String} 65 */ 66 67 function getSource($el) { 68 var source, slug; 69 var data = $el.data(); 70 if (data.pattern && data.widths && $.isArray(data.widths)) { 71 source = retina ? data.patternRetina : data.pattern; 72 source = source || data.pattern; 73 74 // width or slug version? 75 if (typeof data.widths[0] === 'object') { 76 slug = (function() { 77 var widths = $.map(data.widths, function(val) { 78 return val.size; 79 }); 80 81 var bestFitWidth = bestFit($el.width(), widths); 82 83 // match best width back to its corresponding slug 84 for (var i = data.widths.length - 1; i >= 0; i--) { 85 if (data.widths[i].size === bestFitWidth) { 86 return data.widths[i].slug; 87 } 88 } 89 })(); 90 91 source = source.replace(options.sizePattern, slug); 92 } else { 93 source = source.replace(options.sizePattern, bestFit($el.width(), data.widths)); 94 } 95 } else { 96 source = retina ? data.srcRetina : data.src; 97 source = source || data.src; 98 } 99 100 return source; 101 } 102 103 /** 104 * Reflect loaded state in class names 105 * and fire event. 106 * 107 * @param {jQuery Object} $el 108 */ 109 function onLoad($el) { 110 $el.addClass('ll-loaded').removeClass('ll-notloaded'); 111 $el.trigger('loaded'); 112 113 if (typeof callback === 'function') { 114 callback.call($el); 115 } 116 } 117 118 /** 119 * Attach event handler that sets correct
120 * media source for the elements' width, or 121 * allows callback to manipulate element 122 * exclusively. 123 */ 124 125 function bindLoader() { 126 $elements.one('laziestloader', function() { 127 var $el = $(this); 128 var source; 129 130 // set height? 131 if ($el.data().ratio) { 132 setHeight.call(this); 133 } 134 135 // set content. default: set element source 136 if (options.setSourceMode) { 137 source = options.getSource($el); 138 if (source && this.getAttribute('src') !== source) { 139 this.setAttribute('src', source); 140 } 141 } 142 143 // applied immediately to reflect that media has started but, 144 // perhaps, hasn't finished downloading. 145 $el.addClass('ll-loadstarted'); 146 147 // Determine when to fire `loaded` event. Wait until 148 // media is truly loaded if possible, otherwise immediately. 149 if (options.setSourceMode && (this.nodeName === 'IMG' || this.nodeName === 'VIDEO' || this.nodeName === 'AUDIO') ) { 150 if (this.nodeName === 'IMG') { 151 this.onload = function() { 152 onLoad($el); 153 }; 154 } else { 155 this.onloadstart = function() { 156 onLoad($el); 157 }; 158 } 159 } else { 160 onLoad($el); 161 } 162 }); 163 } 164 165 /** 166 * Remove even handler from elements 167 */ 168 169 function unbindLoader() { 170 $elements.off('laziestloader'); 171 } 172 173 /** 174 * Find the best sized image, opting for larger over smaller 175 * 176 * @param {Number} targetWidth element width 177 * @param {Array} widths array of numbers 178 * @return {Number} 179 */ 180 181 var bestFit = laziestLoader.bestFit = function(targetWidth, widths) { 182 var selectedWidth = widths[widths.length - 1], 183 i = widths.length, 184 offset = targetWidth * (options.sizeOffsetPercent / 100); 185 186 // sort smallest to largest 187 widths.sort(function(a, b) { 188 return a - b; 189 }); 190 191 while (i--) { 192 if ((targetWidth - offset) <= widths[i]) { 193 selectedWidth = widths[i]; 194 } 195 } 196 197 return selectedWidth; 198 }; 199 200 /** 201 * Cycle through elements that haven't had their 202 * source set and, if they're in the viewport within 203 * the threshold, load their media 204 */ 205 206 function laziestloader() { 207 var docEl = document.documentElement; 208 var wHeight = window.innerHeight || docEl.clientHeight; 209 var wWidth = window.innerWidth || docEl.clientWidth; 210 var threshold = options.threshold; 211 212 var $inview = $elements.not($loaded).filter(function() { 213 if ($(this).is(':hidden')) return; 214 var rect = $(this)[0].getBoundingClientRect(); 215 216 return ( 217 rect.bottom + threshold > 0 && 218 rect.right + threshold > 0 && 219 rect.left < wWidth + threshold && 220 rect.top < wHeight + threshold 221 ); 222 }); 223 224 $inview.trigger('laziestloader'); 225 $loaded.add($inview); 226 } 227 228 229 /** 230 * Given a lazy element, check if it should have 231 * its height set based on a data-ratio multiplier. 232 */ 233 function setHeight() { 234 var $el = $(this), 235 data = $el.data(); 236 237 data.ratio = data.ratio || data.heightMultiplier; // backwards compatible for old data-height-multiplier code. 238 239 if (data.ratio) { 240 $el.css({ 241 height: Math.round($el.width() * data.ratio) 242 }); 243 } 244 } 245 246 // add inital state classes, and check if 247 // element dimensions need to be set. 248 $elements.addClass('ll-init ll-notloaded').each(setHeight); 249 250 // initial binding 251 bindLoader(); 252 253 254 // Watch either native scroll events, throttled by 255 // options.scrollThrottle, or a custom event that 256 // implements its own throttling. 257 258 if (useNativeScroll) { 259 $w.scroll(function(){ 260 didScroll = true; 261 }); 262 263 setInterval(function() { 264 if (didScroll) { 265 didScroll = false; 266 laziestloader(); 267 } 268 }, options.scrollThrottle); 269 270 } else { 271 272 // if custom event is a function, it'll need 273 // to call laziestloader() manually, like so: 274 // 275 // $('.g-lazy').laziestloader({ 276 // event: function(cb){
277 // // custom scroll event on nytimes.com 278 // PageManager.on('nyt:page-scroll', function(){ 279 // // do something interesting if you like 280 // // and then call the passed in laziestloader(); 281 // cb(); 282 // }); 283 // } 284 // }); 285 // 286 // 287 // Otherwise, it's a string representing an event on the 288 // window to subscribe to, like so: 289 // 290 // // some code dispatching throttled events 291 // $window.trigger('nytg-scroll'); 292 // 293 // $('.g-lazy').laziestloader({ 294 // event: 'nytg-scroll' 295 // }); 296 // 297 298 if (typeof options.event === 'function') { 299 options.event(laziestloader); 300 } else { 301 $w.on(options.event, function(){ 302 laziestloader(); 303 }); 304 } 305 } 306 307 308 309 // reset state on resize 310 $w.resize(function() { 311 $loaded = $(); 312 unbindLoader(); 313 bindLoader(); 314 laziestloader(); 315 }); 316 317 // initial check for lazy images 318 $(document).ready(function() { 319 laziestloader(); 320 }); 321 322 return this; 323 }; 324 325 $.fn.laziestloader = laziestLoader; 326 327}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.