nprogress.js 11 KB

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