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