1/* 2* JAIL: jQuery Asynchronous Image Loader 3* 4* Copyright (c) 2011-12 Sebastiano Armeli-Battana (http://www.sebastianoarmelibattana.com) 5* 6* By Sebastiano Armeli-Battana (@sebarmeli) 7* Licensed under the MIT license. 8* https://github.com/sebarmeli/JAIL/blob/master/MIT-LICENSE.txt 9* 10* Tested with jQuery 1.3.2+ on FF 2+, Opera 10+, Safari 4+, Chrome 8+ on Win/Mac/Linux 11* and IE 6/7/8 on Win. 12* 13* Contributor : Derek Lindahl - @dlindahl 14* 15* @link http://github.com/sebarmeli/JAIL 16* @author Sebastiano Armeli-Battana 17* @date 14/10/2012 18* @version 1.0.0 19* 20*/ 21;(function ( name, definition ){ 22 23 var theModule = definition(jQuery), 24 hasDefine = typeof define === 'function' && define.amd; 25 26 if ( hasDefine ){ // AMD module 27 28 define( name , ['jquery'], theModule ); 29 30 } else { // assign 'jail' to global objects 31 32 ( this.jQuery || this.$ || this )[name] = theModule; 33 34 } 35}( 'jail', function ($) { 36 37 var $window = $( window ), 38 39 // Defaults parameters 40 defaults = { 41 id : 'jail', 42 timeout : 1, 43 effect : false, 44 speed : 400, 45 triggerElement: null, 46 offset : 0, 47 event : 'load', 48 callback : null, 49 callbackAfterEachImage : null, 50 placeholder : false, 51 loadHiddenImages : false 52 }, 53 54 // current stack of images 55 currentStack = [], 56 57 // true if 'callback' fn is called 58 isCallbackDone = false; 59 60 /* 61 * Public function defining 'jail' 62 * 63 * @module jail 64 * @param elems : images to load - jQuery elements 65 * @param opts : configurations object 66 */ 67 $.jail = function( elems, opts ) { 68 69 var elements = elems || {}, 70 options = $.extend( {}, defaults, opts ); 71 72 // Initialize plugin 73 $.jail.prototype.init( elements, options ); 74 75 // When the event is not specified the images will be loaded with a delay 76 if(/^(load|scroll)/.test( options.event )) { 77 // 'load' event 78 $.jail.prototype.later.call( elements, options ); 79 } else { 80 $.jail.prototype.onEvent.call( elements, options ); 81 } 82 }; 83 84 /* 85 * Method in charge of initializing the plugin, storing 86 * the 'element triggering the image to load' in a data attribute 87 * for each image and displaying the placeholder image (if existing) 88 * 89 * @method init 90 * @param {Array} elems Images to load - jQuery elements 91 * @param {Object} opts Configurations object 92 */ 93 $.jail.prototype.init = function( elements, options ) { 94 95 // Store the selector triggering jail into 'triggerElem' data for the images selected 96 elements.data("triggerElem", ( options.triggerElement ) ? $( options.triggerElement ) : $window ); 97 98 // Use a placeholder in case it is specified 99 if ( !!options.placeholder ) { 100 elements.each(function(){ 101 $(this).attr( "src", options.placeholder ); 102 }); 103 } 104 }; 105 106 107 /* 108 * Function called when 'event' is different from "load" or "scroll". Two scenarios: 109 * a) Element triggering the images to be loaded (events available on the element: "click", "mouseover", "scroll") 110 * b) Event on the image itself triggering the image to be loaded 111 * 112 * @param options : configurations object 113 */ 114 $.jail.prototype.onEvent = function( options ) { 115 var images = this; 116
117 if (!!options.triggerElement) { 118 119 // Event on the 'triggerElement' obj 120 _bindEvent( options, images ); 121 122 } else { 123 124 // Event on the image itself 125 images.on( options.event + '.' + options.id, {options: options, images: images}, function(e) { 126 var $img = $(this), 127 options = e.data.options, 128 images = e.data.images; 129 130 currentStack = $.extend( {}, images ); 131 132 // Load the image 133 _loadImage( options, $img ); 134 135 // Image has been loaded so there is no need to listen anymore 136 $(e.currentTarget).unbind( e.type + '.' + options.id ); 137 }); 138 } 139 }; 140 141 /* 142 * Method called when "event" is equals to "load" (default) or "scroll". The visible images will be 143 * loaded after a specified timeout (or after 1 ms). The scroll method will be bound to the window 144 * to load the images not visible onload. 145 * 146 * @param options : configurations object 147 */ 148 $.jail.prototype.later = function( options ) { 149 var images = this; 150 151 // After [timeout] has elapsed, load the visible images 152 setTimeout(function() { 153 154 currentStack = $.extend( {}, images ); 155 156 //Load the visible ones 157 images.each(function(){ 158 _loadImageIfVisible( options, this, images ); 159 }); 160 161 // When images become available (scrolling or resizing), they will be loaded 162 options.event = "scroll"; 163 _bindEvent( options, images ); 164 165 }, options.timeout); 166 }; 167 168 /* 169 * Bind _bufferedEventListener() to the event on window/triggerElement. The handler is bound to 170 * resizing the window as well 171 * 172 * @param options : configurations object 173 * @param images : images in the current stack 174 */ 175 function _bindEvent ( options, images ) { 176 var triggerElem = false; 177 178 if (!!images) { 179 triggerElem = images.data("triggerElem"); 180 } 181 182 // Check if there are images to load 183 if (!!triggerElem && typeof triggerElem.on === "function") {
184 triggerElem.on( options.event + '.' + options.id, {options:options, images : images}, _bufferedEventListener ); 185 $window.on( 'resize.'+options.id, {options:options, images : images}, _bufferedEventListener ); 186 } 187 } 188 189 /* 190 * Remove any elements that have been loaded from the jQuery stack. 191 * This should speed up subsequent calls by not having to iterate over the loaded elements. 192 * 193 * @param stack : current images stack 194 */ 195 function _purgeStack ( stack ) { 196 // number of images not loaded 197 var i = 0; 198 199 if (stack.length === 0) { return; } 200 201 // Check on existence of 'data-src' attribute to verify if the image has been loaded 202 while(true) { 203 if(i === stack.length) { 204 break; 205 } else { 206 if ($(stack[i]).attr('data-src')) { 207 i++; 208 } else { 209 stack.splice( i, 1 ); 210 } 211 } 212 } 213 } 214 215 /* 216 * Event handler for the images to be loaded. Function called when 217 * there is a triggerElement or when there are images to be loaded after scrolling 218 * or resizing window/container 219 * 220 * @param e : event 221 */ 222 function _bufferedEventListener (e) { 223 var images = e.data.images, 224 options = e.data.options; 225 // Don't set a timer if one is set. 226 if (images.data("poller")) {return;} 227 228 images.data('poller', setTimeout(function() { 229 230 currentStack = $.extend( {}, images ); 231 _purgeStack(currentStack); 232 233 // Load only the images left 234 $(currentStack).each(function (){ 235 if (this === window) { 236 return; 237 } 238 _loadImageIfVisible(options, this, currentStack); 239 }); 240 241 //Unbind when there are no images 242 if ( _isAllImagesLoaded (currentStack) ) { 243 $(e.currentTarget).unbind( e.type + '.' + options.id ); 244 return; 245 } 246 // When images are not in the viewport, let's load them when they become available 247 else if (options.event !== "scroll"){ 248 249 // When images become available (scrolling or resizing), they will be loaded 250 var container = (/scroll/i.test(options.event)) ? images.data("triggerElem") : $window; 251 252 options.event = "scroll"; 253 images.data("triggerElem", container); 254 _bindEvent( options, $(currentStack) ); 255 } 256 images.data("poller", null); 257 }, options.timeout)); 258 } 259 260 /* 261 * Check if all the images are loaded 262 * 263 * @param images : images under analysis 264 * @return boolean 265 */ 266 function _isAllImagesLoaded ( images ) { 267 var bool = true; 268 269 $(images).each(function(){ 270 if ( !!$(this).attr("data-src") ) { 271 bool = false;
272 } 273 }); 274 return bool; 275 } 276 277 /* 278 * Load the image if visible in the viewport 279 * 280 * @param options : configurations object 281 * @param image : image under analysis 282 * @param images : list of images to load 283 */ 284 function _loadImageIfVisible ( options, image, images ) { 285 var $img = $(image), 286 container = (/scroll/i.test(options.event)) ? images.data("triggerElem") : $window, 287 isVisible = true; 288 289 // If don't you want to load hidden images (default beahviour) 290 if ( !options.loadHiddenImages ) { 291 isVisible = _isVisibleInContainer( $img, container, options ) && $img.is(":visible"); 292 } 293 294 // Load the image if it is not hidden and visible in the screen 295 if( isVisible && _isInTheScreen( container, $img, options.offset ) ) { 296 _loadImage( options, $img ); 297 } 298 } 299 300 /* 301 * Function that returns true if the image is visible inside the "window" (or specified container element) 302 * 303 * @param $ct : container - jQuery obj 304 * @param $img : image selected - jQuery obj 305 * @param optionOffset : offset 306 */ 307 function _isInTheScreen ( $ct, $img, optionOffset ) { 308 var is_ct_window = $ct[0] === window, 309 ct_offset = (is_ct_window ? { top:0, left:0 } : $ct.offset()), 310 ct_top = ct_offset.top + ( is_ct_window ? $ct.scrollTop() : 0), 311 ct_left = ct_offset.left + ( is_ct_window ? $ct.scrollLeft() : 0), 312 ct_right = ct_left + $ct.width(), 313 ct_bottom = ct_top + $ct.height(), 314 img_offset = $img.offset(), 315 img_width = $img.width(), 316 img_height = $img.height(); 317 318 return (ct_top - optionOffset) <= (img_offset.top + img_height) && 319 (ct_bottom + optionOffset) >= img_offset.top && 320 (ct_left - optionOffset)<= (img_offset.left + img_width) && 321 (ct_right + optionOffset) >= img_offset.left; 322 } 323 324 /* 325 * Main function --> Load the images copying the "data-href" attribute into the "src" attribute 326 * 327 * @param options : configurations object 328 * @param $img : image selected - jQuery obj 329 */ 330 function _loadImage ( options, $img ) { 331 332 // Use cache Image object to show images only when ready 333 var cache = new Image(); 334 335 cache.onload = function() { 336 $img.hide().attr("src", cache.src); 337 338 $img.removeAttr('data-src'); 339 // Images loaded with some effect if existing 340 if( options.effect) { 341 342 if ( options.speed ) { 343 $img[options.effect](options.speed); 344 } else { 345 $img[options.effect](); 346 } 347 $img.css("opacity", 1); 348 $img.show(); 349 } else { 350 $img.show(); 351 } 352 353 _purgeStack(currentStack); 354 355 // Callback after each image is loaded 356 if ( !!options.callbackAfterEachImage ) { 357 options.callbackAfterEachImage.call( this, $img, options ); 358 } 359 360 if ( _isAllImagesLoaded (currentStack) && !!options.callback && !isCallbackDone ) { 361 options.callback.call($.jail, options); 362 isCallbackDone = true; 363 } 364 }; 365 366 /* 367 * Basic on error handler. For now we limit 368 * the plugin to exectute any callback passed 369 * in the options object. Let the developer 370 * decide what to do with it. 371 */ 372 cache.onerror = function(){ 373 374 if(!('error' in options)) { return; } 375 376 var args = Array.prototype.slice.call(arguments, 0); 377 args = [$img, options].concat(args); 378 options.error.apply($.jail, args); 379 }; 380 381 cache.src = $img.attr("data-src"); 382 } 383 384 /* 385 * Return if the image is visible inside a "container" / window. There are checks around 386 * "visibility" CSS property and around "overflow" property of the "container" 387 * 388 * @param $img : image selected - jQuery obj 389 * @param container : container object 390 * @param options : configurations object 391 */ 392 function _isVisibleInContainer ( $img, container, options ){ 393 394 var parent = $img.parent(), 395 isVisible = true; 396 397 while ( parent.length && parent.get(0).nodeName.toUpperCase() !== "BODY" ) { 398 // Consider the 'overflow' property 399 if ( parent.css("overflow") === "hidden" ) { 400 if (!_isInTheScreen(parent, $img, options.offset)) { 401 isVisible = false; 402 break; 403 } 404 } else if ( parent.css("overflow") === "scroll" ) { 405 if (!_isInTheScreen(parent, $img, options.offset)) { 406 isVisible = false; 407 $(currentStack).data("triggerElem", parent); 408 409 options.event = "scroll"; 410 _bindEvent(options, $(currentStack)); 411 break; 412 } 413 } 414 415 if ( parent.css("visibility") === "hidden" || $img.css("visibility") === "hidden" ) { 416 isVisible = false;
417 break; 418 } 419 420 // If container is not the window, and the parent is the container, exit from the loop 421 if ( container !== $window && parent === container ) { 422 break; 423 } 424 425 parent = parent.parent(); 426 } 427 428 return isVisible; 429 } 430 431 // Small wrapper 432 $.fn.jail = function( options ) { 433 434 new $.jail( this, options ); 435 436 // Empty current stack 437 currentStack = []; 438 439 return this; 440 }; 441 442 return $.jail; 443}));
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.