nprogress.js (11565B)
1 /* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress 2 * @license MIT */ 3 4 ;(function(root, factory) { 5 6 if (typeof define === 'function' && define.amd) { 7 define(factory); 8 } else if (typeof exports === 'object') { 9 module.exports = factory(); 10 } else { 11 root.NProgress = factory(); 12 } 13 14 })(this, function() { 15 var NProgress = {}; 16 17 NProgress.version = '0.2.0'; 18 19 var Settings = NProgress.settings = { 20 minimum: 0.08, 21 easing: 'ease', 22 positionUsing: '', 23 speed: 200, 24 trickle: true, 25 trickleRate: 0.02, 26 trickleSpeed: 800, 27 showSpinner: true, 28 barSelector: '[role="bar"]', 29 spinnerSelector: '[role="spinner"]', 30 parent: 'body', 31 template: '<div class="bar" role="bar"><div class="peg"></div></div><div class="spinner" role="spinner"><div class="spinner-icon"></div></div>' 32 }; 33 34 /** 35 * Updates configuration. 36 * 37 * NProgress.configure({ 38 * minimum: 0.1 39 * }); 40 */ 41 NProgress.configure = function(options) { 42 var key, value; 43 for (key in options) { 44 value = options[key]; 45 if (value !== undefined && options.hasOwnProperty(key)) Settings[key] = value; 46 } 47 48 return this; 49 }; 50 51 /** 52 * Last number. 53 */ 54 55 NProgress.status = null; 56 57 /** 58 * Sets the progress bar status, where `n` is a number from `0.0` to `1.0`. 59 * 60 * NProgress.set(0.4); 61 * NProgress.set(1.0); 62 */ 63 64 NProgress.set = function(n) { 65 var started = NProgress.isStarted(); 66 67 n = clamp(n, Settings.minimum, 1); 68 NProgress.status = (n === 1 ? null : n); 69 70 var progress = NProgress.render(!started), 71 bar = progress.querySelector(Settings.barSelector), 72 speed = Settings.speed, 73 ease = Settings.easing; 74 75 progress.offsetWidth; /* Repaint */ 76 77 queue(function(next) { 78 // Set positionUsing if it hasn't already been set 79 if (Settings.positionUsing === '') Settings.positionUsing = NProgress.getPositioningCSS(); 80 81 // Add transition 82 css(bar, barPositionCSS(n, speed, ease)); 83 84 if (n === 1) { 85 // Fade out 86 css(progress, { 87 transition: 'none', 88 opacity: 1 89 }); 90 progress.offsetWidth; /* Repaint */ 91 92 setTimeout(function() { 93 css(progress, { 94 transition: 'all ' + speed + 'ms linear', 95 opacity: 0 96 }); 97 setTimeout(function() { 98 NProgress.remove(); 99 next(); 100 }, speed); 101 }, speed); 102 } else { 103 setTimeout(next, speed); 104 } 105 }); 106 107 return this; 108 }; 109 110 NProgress.isStarted = function() { 111 return typeof NProgress.status === 'number'; 112 }; 113 114 /** 115 * Shows the progress bar. 116 * This is the same as setting the status to 0%, except that it doesn't go backwards. 117 * 118 * NProgress.start(); 119 * 120 */ 121 NProgress.start = function() { 122 if (!NProgress.status) NProgress.set(0); 123 124 var work = function() { 125 setTimeout(function() { 126 if (!NProgress.status) return; 127 NProgress.trickle(); 128 work(); 129 }, Settings.trickleSpeed); 130 }; 131 132 if (Settings.trickle) work(); 133 134 return this; 135 }; 136 137 /** 138 * Hides the progress bar. 139 * This is the *sort of* the same as setting the status to 100%, with the 140 * difference being `done()` makes some placebo effect of some realistic motion. 141 * 142 * NProgress.done(); 143 * 144 * If `true` is passed, it will show the progress bar even if its hidden. 145 * 146 * NProgress.done(true); 147 */ 148 149 NProgress.done = function(force) { 150 if (!force && !NProgress.status) return this; 151 152 return NProgress.inc(0.3 + 0.5 * Math.random()).set(1); 153 }; 154 155 /** 156 * Increments by a random amount. 157 */ 158 159 NProgress.inc = function(amount) { 160 var n = NProgress.status; 161 162 if (!n) { 163 return NProgress.start(); 164 } else { 165 if (typeof amount !== 'number') { 166 amount = (1 - n) * clamp(Math.random() * n, 0.1, 0.95); 167 } 168 169 n = clamp(n + amount, 0, 0.994); 170 return NProgress.set(n); 171 } 172 }; 173 174 NProgress.trickle = function() { 175 return NProgress.inc(Math.random() * Settings.trickleRate); 176 }; 177 178 /** 179 * Waits for all supplied jQuery promises and 180 * increases the progress as the promises resolve. 181 * 182 * @param $promise jQUery Promise 183 */ 184 (function() { 185 var initial = 0, current = 0; 186 187 NProgress.promise = function($promise) { 188 if (!$promise || $promise.state() === "resolved") { 189 return this; 190 } 191 192 if (current === 0) { 193 NProgress.start(); 194 } 195 196 initial++; 197 current++; 198 199 $promise.always(function() { 200 current--; 201 if (current === 0) { 202 initial = 0; 203 NProgress.done(); 204 } else { 205 NProgress.set((initial - current) / initial); 206 } 207 }); 208 209 return this; 210 }; 211 212 })(); 213 214 /** 215 * (Internal) renders the progress bar markup based on the `template` 216 * setting. 217 */ 218 219 NProgress.render = function(fromStart) { 220 if (NProgress.isRendered()) return document.getElementById('nprogress'); 221 222 addClass(document.documentElement, 'nprogress-busy'); 223 224 var progress = document.createElement('div'); 225 progress.id = 'nprogress'; 226 progress.innerHTML = Settings.template; 227 228 var bar = progress.querySelector(Settings.barSelector), 229 perc = fromStart ? '-100' : toBarPerc(NProgress.status || 0), 230 parent = document.querySelector(Settings.parent), 231 spinner; 232 233 css(bar, { 234 transition: 'all 0 linear', 235 transform: 'translate3d(' + perc + '%,0,0)' 236 }); 237 238 if (!Settings.showSpinner) { 239 spinner = progress.querySelector(Settings.spinnerSelector); 240 spinner && removeElement(spinner); 241 } 242 243 if (parent != document.body) { 244 addClass(parent, 'nprogress-custom-parent'); 245 } 246 247 parent.appendChild(progress); 248 return progress; 249 }; 250 251 /** 252 * Removes the element. Opposite of render(). 253 */ 254 255 NProgress.remove = function() { 256 removeClass(document.documentElement, 'nprogress-busy'); 257 removeClass(document.querySelector(Settings.parent), 'nprogress-custom-parent'); 258 var progress = document.getElementById('nprogress'); 259 progress && removeElement(progress); 260 }; 261 262 /** 263 * Checks if the progress bar is rendered. 264 */ 265 266 NProgress.isRendered = function() { 267 return !!document.getElementById('nprogress'); 268 }; 269 270 /** 271 * Determine which positioning CSS rule to use. 272 */ 273 274 NProgress.getPositioningCSS = function() { 275 // Sniff on document.body.style 276 var bodyStyle = document.body.style; 277 278 // Sniff prefixes 279 var vendorPrefix = ('WebkitTransform' in bodyStyle) ? 'Webkit' : 280 ('MozTransform' in bodyStyle) ? 'Moz' : 281 ('msTransform' in bodyStyle) ? 'ms' : 282 ('OTransform' in bodyStyle) ? 'O' : ''; 283 284 if (vendorPrefix + 'Perspective' in bodyStyle) { 285 // Modern browsers with 3D support, e.g. Webkit, IE10 286 return 'translate3d'; 287 } else if (vendorPrefix + 'Transform' in bodyStyle) { 288 // Browsers without 3D support, e.g. IE9 289 return 'translate'; 290 } else { 291 // Browsers without translate() support, e.g. IE7-8 292 return 'margin'; 293 } 294 }; 295 296 /** 297 * Helpers 298 */ 299 300 function clamp(n, min, max) { 301 if (n < min) return min; 302 if (n > max) return max; 303 return n; 304 } 305 306 /** 307 * (Internal) converts a percentage (`0..1`) to a bar translateX 308 * percentage (`-100%..0%`). 309 */ 310 311 function toBarPerc(n) { 312 return (-1 + n) * 100; 313 } 314 315 316 /** 317 * (Internal) returns the correct CSS for changing the bar's 318 * position given an n percentage, and speed and ease from Settings 319 */ 320 321 function barPositionCSS(n, speed, ease) { 322 var barCSS; 323 324 if (Settings.positionUsing === 'translate3d') { 325 barCSS = { transform: 'translate3d('+toBarPerc(n)+'%,0,0)' }; 326 } else if (Settings.positionUsing === 'translate') { 327 barCSS = { transform: 'translate('+toBarPerc(n)+'%,0)' }; 328 } else { 329 barCSS = { 'margin-left': toBarPerc(n)+'%' }; 330 } 331 332 barCSS.transition = 'all '+speed+'ms '+ease; 333 334 return barCSS; 335 } 336 337 /** 338 * (Internal) Queues a function to be executed. 339 */ 340 341 var queue = (function() { 342 var pending = []; 343 344 function next() { 345 var fn = pending.shift(); 346 if (fn) { 347 fn(next); 348 } 349 } 350 351 return function(fn) { 352 pending.push(fn); 353 if (pending.length == 1) next(); 354 }; 355 })(); 356 357 /** 358 * (Internal) Applies css properties to an element, similar to the jQuery 359 * css method. 360 * 361 * While this helper does assist with vendor prefixed property names, it 362 * does not perform any manipulation of values prior to setting styles. 363 */ 364 365 var css = (function() { 366 var cssPrefixes = [ 'Webkit', 'O', 'Moz', 'ms' ], 367 cssProps = {}; 368 369 function camelCase(string) { 370 return string.replace(/^-ms-/, 'ms-').replace(/-([\da-z])/gi, function(match, letter) { 371 return letter.toUpperCase(); 372 }); 373 } 374 375 function getVendorProp(name) { 376 var style = document.body.style; 377 if (name in style) return name; 378 379 var i = cssPrefixes.length, 380 capName = name.charAt(0).toUpperCase() + name.slice(1), 381 vendorName; 382 while (i--) { 383 vendorName = cssPrefixes[i] + capName; 384 if (vendorName in style) return vendorName; 385 } 386 387 return name; 388 } 389 390 function getStyleProp(name) { 391 name = camelCase(name); 392 return cssProps[name] || (cssProps[name] = getVendorProp(name)); 393 } 394 395 function applyCss(element, prop, value) { 396 prop = getStyleProp(prop); 397 element.style[prop] = value; 398 } 399 400 return function(element, properties) { 401 var args = arguments, 402 prop, 403 value; 404 405 if (args.length == 2) { 406 for (prop in properties) { 407 value = properties[prop]; 408 if (value !== undefined && properties.hasOwnProperty(prop)) applyCss(element, prop, value); 409 } 410 } else { 411 applyCss(element, args[1], args[2]); 412 } 413 } 414 })(); 415 416 /** 417 * (Internal) Determines if an element or space separated list of class names contains a class name. 418 */ 419 420 function hasClass(element, name) { 421 var list = typeof element == 'string' ? element : classList(element); 422 return list.indexOf(' ' + name + ' ') >= 0; 423 } 424 425 /** 426 * (Internal) Adds a class to an element. 427 */ 428 429 function addClass(element, name) { 430 var oldList = classList(element), 431 newList = oldList + name; 432 433 if (hasClass(oldList, name)) return; 434 435 // Trim the opening space. 436 element.className = newList.substring(1); 437 } 438 439 /** 440 * (Internal) Removes a class from an element. 441 */ 442 443 function removeClass(element, name) { 444 var oldList = classList(element), 445 newList; 446 447 if (!hasClass(element, name)) return; 448 449 // Replace the class name. 450 newList = oldList.replace(' ' + name + ' ', ' '); 451 452 // Trim the opening and closing spaces. 453 element.className = newList.substring(1, newList.length - 1); 454 } 455 456 /** 457 * (Internal) Gets a space separated list of the class names on the element. 458 * The list is wrapped with a single space on each end to facilitate finding 459 * matches within the list. 460 */ 461 462 function classList(element) { 463 return (' ' + (element.className || '') + ' ').replace(/\s+/gi, ' '); 464 } 465 466 /** 467 * (Internal) Removes an element from the DOM. 468 */ 469 470 function removeElement(element) { 471 element && element.parentNode && element.parentNode.removeChild(element); 472 } 473 474 return NProgress; 475 }); 476