1(function($) { 2 /* 3 ** Defaults: 4 ** 5 ** preloadSelector 6 ** If set to true will preload the selector's background image, note 7 ** that the image will show as soon as it is loaded and not only 8 ** once the preloader is done loading. 9 ** 10 ** truePercentage 11 ** NOTE: does not work with cross-domain calls. 12 ** If set to true will get the actual (compressed) file size of all 13 ** the images instead of just looking at the number of images loaded 14 ** divided by the total number of images. 15 ** 16 ** disableOverlay 17 ** If set to true will not create the blocking overlay. 18 ** 19 ** showInContainer 20 ** If set to true will load the preloader inside the selector element 21 ** instead of across the whole page. 22 ** 23 ** hideBackground 24 ** NOTE: hideBackground is an option for when showInContainer is set 25 ** to true. 26 ** If set to true will hide the css background-image of the selector 27 ** element. 28 ** 29 ** hideNonImageElements 30 ** If set to true will hide all elements of the selector, not only 31 ** the images. 32 ** 33 ** progressiveReveal 34 ** NOTE: if hideNonImageElements is set to true then progessiveReveal 35 ** might not return the expected result because the image element's 36 ** parent might be hidden. 37 ** If set to true will show images as soon as they are preloaded. 38 ** 39 ** forceSequentialLoad 40 ** Will force images to load in the order they appear in the DOM, this 41 ** can potentially increase the load time because images won't start 42 ** loading in parallel. 43 ** 44 ** silentMode 45 ** If set to true will hide the preloader. 46 ** 47 ** debugMode 48 ** If set to true will show errors. 49 ** 50 ** useOpacity 51 ** If set to true will use opacity property to hide elements instead 52 ** of display property. 53 ** 54 ** hidePercentage 55 ** If set to true will not show the percentage numbers while loading. 56 ** 57 ** loaderText 58 ** Set the text of the loading message. 59 ** 60 ** animateDuration 61 ** Set the duration in milliseconds for each progress animation. 62 ** 63 ** fadeOutDuration 64 ** Set the duration in milliseconds for the preloader fadeout animation. 65 ** 66 ** showImagesBeforeComplete 67 ** If set to false will wait for the animation of the preloader fadeout 68 ** to complete before showing the images. 69 ** 70 ** afterEach 71 ** Called once after every image load. 72 ** 73 ** beforeComplete 74 ** Called once after all images have been loaded and before the fadeout 75 ** animation of the preloader triggers. 76 ** 77 ** onComplete 78 ** Called once after all images have been loaded and all preloader 79 ** animations have completed. 80 */ 81 82 var defaults = { 83 selector: false, 84 preloadSelector : true, 85 truePercentage : false, 86 disableOverlay : false, 87 showInContainer : false, 88 hideBackground : false, 89 hideNonImageElements : false, 90 progressiveReveal : false, 91 forceSequentialLoad : false, 92 silentMode : false, 93 debugMode : false, 94 useOpacity : false, 95 hidePercentage : false, 96 loaderText : 'loading images, please wait...', 97 animateDuration : 1000, 98 fadeOutDuration : 100, 99 showImagesBeforeComplete : true, 100 afterEach : function() {}, 101 beforeComplete : function() {}, 102 onComplete : function() {} 103 }; 104 105 var methods = { 106 init: function(options) { 107 var settings = $.extend({}, defaults, options); 108 109 return this.each(function() { 110 var parent = $(this); 111 var elementChildren = getAllChildren(parent); 112 var imageElements = []; 113 var nonImageElements = []; 114 var imagesLoaded = 0; 115 var totalImages = 0; 116 var progressPercentage = 0; 117 var totalPercentage = 0; 118 var count = 0; 119 var minUpdateToValue = 0; // prevents preloader from jumping backwards 120 121 // Create preloader DOM elements.
122 var preloadContainer = $('<div id="crp_preloader"></div>'); 123 124 if (!settings.showInContainer) { 125 preloadContainer.appendTo('body'); 126 preloadContainer.css('position', 'fixed'); 127 } else { 128 preloadContainer.appendTo(parent); 129 parent.css('position', 'relative'); 130 preloadContainer.css('position', 'absolute'); 131 } 132 133 if (!settings.disableOverlay) { 134 var preloadOverlay = $('<div class="crp_loader_overlay"></div>').appendTo(preloadContainer); 135 } else { 136 preloadContainer.css('height', 'auto'); 137 } 138 139 var preloadLoader = $('<div class="crp_loader"></div>').appendTo(preloadContainer); 140 141 if (!settings.hidePercentage) { 142 var progressNotification = $('<div class="crp_progress_notification">' + settings.loaderText + ' <span class="crp_progress_percentage">' + progressPercentage + '</span>%</div>').appendTo(preloadLoader); 143 } else { 144 var progressNotification = $('<div class="crp_progress_notification">' + settings.loaderText + '</div>').appendTo(preloadLoader); 145 } 146 147 var progressBar = $('<div class="crp_progress_bar"></div>').appendTo(preloadLoader); 148 var progressLoaded = $('<div class="crp_progress_loaded"></div>').appendTo(progressBar); 149 150 if (settings.silentMode) { 151 preloadContainer.hide(); 152 } 153 154 // Start animating progress bar to indicate activity 155 if (settings.truePercentage) { 156 updateProgressbar(1, 3000); // 3s initial trickle 157 } 158 159 if (settings.debugMode) { 160 var startTime = (new Date).getTime(); 161 162 console.groupCollapsed('crpPreload > ', parent); 163 console.log((((new Date).getTime() - startTime) / 1000).toFixed(3) + ': scanning DOM for image elements...'); 164 console.groupCollapsed('image elements'); 165 } 166 167 /* 168 ** Get all elements that contain images or background images 169 ** to check how many images have to be preloaded. 170 */ 171 elementChildren.forEach(function(child) { 172 if (child.is('img') || child.css('background-image') !== 'none' && child.css('background-image').indexOf('gradient') == -1) { 173 174 // While preloading hide all elements except parent if set to false. 175 if (!(settings.preloadSelector && settings.showInContainer && child === parent)) { 176 if (!settings.useOpacity) { 177 child.hide(); 178 } else { 179 child.css('opacity', '0'); 180 } 181 } else if (settings.hideBackground) { 182 child.attr('data-bg', child.css('background-image')).css('background-image', 'none'); 183 } 184 185 var imageElement = { 186 node : child, 187 fileSize : 0 188 }; 189 190 if (settings.debugMode) { 191 console.log(imageElement.node); 192 } 193 194 imageElements.push(imageElement); 195 totalImages++; 196 } else if (settings.hideNonImageElements) { 197 if (!settings.useOpacity) { 198 child.hide(); 199 } else { 200 child.css('opacity', '0'); 201 } 202 203 nonImageElements.push(child); 204 } 205 }); 206 207 if (settings.debugMode) { 208 console.groupEnd(); 209 console.log((((new Date).getTime() - startTime) / 1000).toFixed(3) + ': scanning DOM for image elements DONE'); 210 } 211 212 /* 213 ** Get the percentage total of all the images. Once this number is reached 214 ** by the preloader the loading is done. 215 */ 216 if (settings.truePercentage) { 217 if (settings.debugMode) { 218 console.log((((new Date).getTime() - startTime) / 1000).toFixed(3) + ': getting image sizes...'); 219 console.groupCollapsed('image sizes'); 220 } 221 222 /* 223 ** Get the Content-Length attribute as rough estimate of the actual 224 ** file size. Use this to get the total file size of all images and 225 ** calculate percentage relative to it. 226 */ 227 imageElements.forEach(function(element) { 228 $.ajax({ 229 type: 'HEAD', 230 cache: false, 231 url: getImageUrl(element.node), 232 success: function(response, message, object) { 233 element.fileSize = parseInt(object.getResponseHeader('Content-Length')); 234 totalPercentage += element.fileSize; 235 236 if (settings.debugMode) { 237 console.log((element.fileSize / 1000).toFixed(2) + ' KB \t' + (totalPercentage / 1000).toFixed(2) + ' KB total'); 238 } 239 240 continueCounting(); 241 }, 242 error: function(object, response, message) { 243 // Ignore errors, they will be handled later. Show a message to notify. 244 continueCounting(); 245 246 var markup = ''; 247 248 markup += 'Not all of your images were preloaded!<br>'; 249 markup += 'Loader failed getting image sizes.<br><br>'; 250 markup += '1. Make sure your images exist.<br>'; 251 markup += '2. Make sure your image paths/urls are correct.<br>'; 252 markup += '3. If you load images from a remote domain set <code>truePercentage: false</code>.<br><br>'; 253 markup += '<button>Close</button>'; 254 255 progressNotification.addClass('error').html(markup); 256 progressBar.addClass('error'); 257 258 settings.fadeOutDuration = 500000; 259 260 $('#crp_preloader button').click(function() { 261 preloadContainer.remove(); 262 }); 263 } 264 }); 265 266 function continueCounting() { 267 count++; 268 269 if (count === totalImages) { 270 if (settings.debugMode) { 271 console.groupEnd(); 272 console.log((((new Date).getTime() - startTime) / 1000).toFixed(3) + ': getting image sizes DONE'); 273 } 274 275 startPreloading(); 276 } 277 } 278 }); 279 } else { 280 if (settings.debugMode) { 281 console.log((((new Date).getTime() - startTime) / 1000).toFixed(3) + ': getting image sizes SKIPPED'); 282 } 283 284 /* 285 ** Get number of total images and use that to calculate percentage 286 ** relative to number of images loaded. 287 */ 288 totalPercentage = totalImages; 289 startPreloading(); 290 } 291 292 function startPreloading() { 293 if (settings.debugMode) { 294 console.log((((new Date).getTime() - startTime) / 1000).toFixed(3) + ': preloading image elements...'); 295 console.groupCollapsed('intervals'); 296 } 297 298 /* 299 ** Get the url of the image or the css background image. Create a 300 ** DOM image element which holds the image (preloads it) and triggers 301 ** a progressbar update on successful load. Show all images or elements 302 ** with background images only once all images have been preloaded. 303 */ 304 if (!settings.forceSequentialLoad) {
305 imageElements.forEach(function(element, index) { 306 var img = element.node; 307 308 img.load(function() { 309 updateLoader(element); 310 }).error(function() { 311 updateLoader(element); 312 handleLoadingError(img, element); 313 }); 314 315 img.attr('src', getImageUrl(element.node)); 316 }); 317 } else { 318 (function loadImage(index) { 319 var currentElement = imageElements[index]; 320 var img = currentElement.node; 321 322 img.load(function() { 323 updateLoader(currentElement); 324 325 if (++index < imageElements.length) { 326 loadImage(index); 327 } 328 }).error(function() { 329 // Ignore failed image loads but add an error class to the image. 330 updateLoader(currentElement); 331 332 if (++index < imageElements.length) { 333 loadImage(index); 334 } 335 handleLoadingError(img, currentElement); 336 }); 337 338 img.attr('src', getImageUrl(currentElement.node)); 339 })(0); 340 } 341 } 342 343 function handleLoadingError(image, element) { 344 element.node.addClass('crp_preloader_not_found_error'); 345 } 346 347 function updateLoader(element) { 348 imagesLoaded++; 349 updateProgressPercentage(element); 350 updateProgressbar(progressPercentage, undefined, element); 351 352 if (settings.progressiveReveal) { 353 revealElement(element.node); 354 } 355 356 settings.afterEach.call(element.node); 357 } 358 359 function updateProgressPercentage(element) { 360 if (settings.truePercentage) { 361 progressPercentage += (element.fileSize / totalPercentage) * 100; 362 363 if (imagesLoaded === totalImages) { 364 /* 365 ** Sometimes truePercentage calculations end up being slighlty 366 ** less or greater than 100% due to JavaScript's handling of 367 ** floating point numbers, so here we make sure the progress 368 ** is 100%. 369 */ 370 progressPercentage = 100; 371 } 372 } else { 373 progressPercentage = (imagesLoaded / totalPercentage) * 100; 374 } 375 } 376 377 function updateProgressbar(value, updateDuration, element) { 378 // Animates the progress bar and percentage to reflect the value specified. 379 updateDuration = updateDuration !== undefined ? updateDuration : settings.animateDuration; 380 var totalWidth = 0; 381 var updateTo = value; 382 var remaining = 0; 383 384 if (updateDuration === undefined && settings.debugMode) { 385 console.log((((new Date).getTime() - startTime) / 1000).toFixed(3) + ': ' + value.toFixed(2) + '%'); 386 } 387 388 /* 389 ** Here we actually set the loader to load less than the actual value. 390 ** We do this so we can creep up do the actual value and prevent the 391 ** loader from ever reaching the value. This will give the impression 392 ** that the loader is constantly loading and not getting stuck as 393 ** often on a random number while waiting for the next animation. 394 */ 395 if (value < 100) { 396 if (element !== undefined) { 397 if (element.round === undefined) { 398 element.round = 0; 399 400 updateTo = value / 4 * 3; 401 remaining = value / 4; 402 } else { 403 element.round++; 404 405 updateTo = value / (4 + element.round) * (3 + element.round); 406 remaining = value / (4 + element.round); 407 updateDuration = 200; 408 } 409 } 410 } 411 412 if (element === undefined) { 413 element = {}; 414 } 415 416 if (element.round === undefined) { 417 element.round = 0; 418 } 419 420 if (updateTo > minUpdateToValue) { 421 minUpdateToValue = updateTo; 422 } 423 424 if (updateTo < minUpdateToValue) { 425 updateTo = minUpdateToValue; 426 } 427 428 progressLoaded.stop(); 429 430 if (element.round < 30) { 431 progressLoaded.animate({'width': updateTo + '%'}, { 432 duration: updateDuration, 433 easing: 'linear', 434 step: function() { 435 /* 436 ** Update totalWidth on each step rather than at top of function 437 ** to ensure that the loading percentage is correct for the odd 438 ** case when the browser window gets resized during loading. 439 */ 440 totalWidth = progressBar.width(); 441 442 // Set the progress percentage text to the currently animated width. 443 progressNotification.children('span').html(Math.round((progressLoaded.width() / totalWidth) * 100)); 444 }, 445 complete: function() { 446 /* 447 ** The "step" function does not always trigger after the animation 448 ** is done, that is why we make sure to always set the value here. 449 ** E.g. we animate width to 100%, but at 99% the "step" function 450 ** might trigger for the last time, which would cause an unwanted 451 ** result. Also, the lower the animation speed is the less accurate 452 ** the "step" gets, leading to scenarios where the progress bar's 453 ** width could be 100% while the text reads something like 87%. 454 */ 455 progressNotification.children('span').html(Math.round(updateTo)); 456 457 // Once done loading show all elements and delete preloader DOM elements. 458 if (imagesLoaded === totalImages) { 459 if (settings.debugMode) { 460 console.groupEnd(); 461 console.log((((new Date).getTime() - startTime) / 1000).toFixed(3) + ': preloading image elements DONE'); 462 console.groupEnd(); 463 } 464 465 progressLoaded.delay(100).queue(function() { 466 if (settings.showImagesBeforeComplete) {
467 imageElements.forEach(function(element) { 468 revealElement(element.node); 469 }); 470 471 nonImageElements.forEach(function(element) { 472 revealElement(element); 473 }); 474 475 settings.beforeComplete.call(this); 476 477 preloadContainer.animate({'opacity':'0'}, settings.fadeOutDuration, function() { 478 preloadContainer.remove(); 479 settings.onComplete.call(this); 480 }); 481 } else { 482 settings.beforeComplete.call(this); 483 484 preloadContainer.animate({'opacity':'0'}, settings.fadeOutDuration, function() { 485 imageElements.forEach(function(element) { 486 revealElement(element.node); 487 }); 488 489 nonImageElements.forEach(function(element) { 490 revealElement(element); 491 }); 492 493 preloadContainer.remove(); 494 settings.onComplete.call(this); 495 }); 496 } 497 }); 498 } else { 499 /* 500 ** Continue animating. We do this to try and prevent the 501 ** loader from getting stuck on a random number. 502 */ 503 updateProgressbar(value, updateDuration, element); 504 } 505 } 506 }); 507 } 508 } 509 510 function getAllChildren(selector) { 511 var selectorChildren = []; 512 513 if (selector.children().length > 0) { 514 if (settings.preloadSelector) { 515 selectorChildren.push(selector); 516 } 517 518 getChildren(selector); 519 } else if (settings.preloadSelector) { 520 selectorChildren.push(selector); 521 } 522 523 function getChildren(element) { 524 var children = element.children(); 525 526 if (children.length > 0) { 527 children.each(function() { 528 var _this = $(this); 529 530 selectorChildren.push(_this); 531 532 if (_this.children().length > 0) { 533 getChildren(_this); 534 } 535 }); 536 } 537 } 538 539 return selectorChildren; 540 } 541 542 function getImageUrl(image) { 543 if (image.css('background-image') !== 'none') { 544 return image.css('background-image').replace(/^url\(["']?/, '').replace(/["']?\)$/, ''); 545 } else if (image.css('background-image') === 'none' && image.attr('data-bg')) { 546 return image.attr('data-bg').replace(/^url\(["']?/, '').replace(/["']?\)$/, ''); 547 } else { 548 return image.attr('data-src'); 549 } 550 } 551 552 function revealElement(element) { 553 if (!settings.useOpacity) { 554 element.show(); 555 } else { 556 element.css('opacity', '1'); 557 } 558 if (element.attr('data-bg')) { 559 element.css('background-image', element.attr('data-bg')); 560 } 561 } 562 }); 563 } 564 }; 565 566 /* 567 ** Check if browser supports Array.forEach() method, if it doesn't 568 ** mimic that functionality, implementation from here: 569 ** http://stackoverflow.com/questions/2790001/fixing-javascript-array-functions-in-internet-explorer-indexof-foreach-etc 570 */ 571 if (!('forEach' in Array.prototype)) { 572 Array.prototype.forEach = function(action, that /*opt*/) { 573 for (var i = 0, n = this.length; i < n; i++) { 574 if (i in this) { 575 action.call(that, this[i], i, this); 576 } 577 } 578 }; 579 } 580 581 $.fn.crpPreload = function(method) { 582 if (methods[method]) { 583 return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 584 } else if (typeof method === 'object' || !method) { 585 return methods.init.apply(this, arguments); 586 } else { 587 $.error('Method ' + method + ' does not exist on jQuery.crpPreload'); 588 } 589 }; 590})(jQuery);
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.