nprogress.js 12 KB

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