countUp.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. (function(root, factory) {
  2. if (typeof define === 'function' && define.amd) {
  3. define(factory);
  4. } else if (typeof exports === 'object') {
  5. module.exports = factory(require, exports, module);
  6. } else {
  7. root.CountUp = factory();
  8. }
  9. }(this, function(require, exports, module) {
  10. /*
  11. countUp.js
  12. by @inorganik
  13. */
  14. // target = id of html element or var of previously selected html element where counting occurs
  15. // startVal = the value you want to begin at
  16. // endVal = the value you want to arrive at
  17. // decimals = number of decimal places, default 0
  18. // duration = duration of animation in seconds, default 2
  19. // options = optional object of options (see below)
  20. var CountUp = function(target, startVal, endVal, decimals, duration, options) {
  21. var self = this;
  22. self.version = function () { return '1.9.3'; };
  23. // default options
  24. self.options = {
  25. useEasing: true, // toggle easing
  26. useGrouping: true, // 1,000,000 vs 1000000
  27. separator: ',', // character to use as a separator
  28. decimal: '.', // character to use as a decimal
  29. easingFn: easeOutExpo, // optional custom easing function, default is Robert Penner's easeOutExpo
  30. formattingFn: formatNumber, // optional custom formatting function, default is formatNumber above
  31. prefix: '', // optional text before the result
  32. suffix: '', // optional text after the result
  33. numerals: [] // optionally pass an array of custom numerals for 0-9
  34. };
  35. // extend default options with passed options object
  36. if (options && typeof options === 'object') {
  37. for (var key in self.options) {
  38. if (options.hasOwnProperty(key) && options[key] !== null) {
  39. self.options[key] = options[key];
  40. }
  41. }
  42. }
  43. if (self.options.separator === '') {
  44. self.options.useGrouping = false;
  45. }
  46. else {
  47. // ensure the separator is a string (formatNumber assumes this)
  48. self.options.separator = '' + self.options.separator;
  49. }
  50. // make sure requestAnimationFrame and cancelAnimationFrame are defined
  51. // polyfill for browsers without native support
  52. // by Opera engineer Erik Möller
  53. var lastTime = 0;
  54. var vendors = ['webkit', 'moz', 'ms', 'o'];
  55. for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
  56. window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
  57. window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
  58. }
  59. if (!window.requestAnimationFrame) {
  60. window.requestAnimationFrame = function(callback, element) {
  61. var currTime = new Date().getTime();
  62. var timeToCall = Math.max(0, 16 - (currTime - lastTime));
  63. var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
  64. lastTime = currTime + timeToCall;
  65. return id;
  66. };
  67. }
  68. if (!window.cancelAnimationFrame) {
  69. window.cancelAnimationFrame = function(id) {
  70. clearTimeout(id);
  71. };
  72. }
  73. function formatNumber(num) {
  74. var neg = (num < 0),
  75. x, x1, x2, x3, i, len;
  76. num = Math.abs(num).toFixed(self.decimals);
  77. num += '';
  78. x = num.split('.');
  79. x1 = x[0];
  80. x2 = x.length > 1 ? self.options.decimal + x[1] : '';
  81. if (self.options.useGrouping) {
  82. x3 = '';
  83. for (i = 0, len = x1.length; i < len; ++i) {
  84. if (i !== 0 && ((i % 3) === 0)) {
  85. x3 = self.options.separator + x3;
  86. }
  87. x3 = x1[len - i - 1] + x3;
  88. }
  89. x1 = x3;
  90. }
  91. // optional numeral substitution
  92. if (self.options.numerals.length) {
  93. x1 = x1.replace(/[0-9]/g, function(w) {
  94. return self.options.numerals[+w];
  95. })
  96. x2 = x2.replace(/[0-9]/g, function(w) {
  97. return self.options.numerals[+w];
  98. })
  99. }
  100. return (neg ? '-' : '') + self.options.prefix + x1 + x2 + self.options.suffix;
  101. }
  102. // Robert Penner's easeOutExpo
  103. function easeOutExpo(t, b, c, d) {
  104. return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b;
  105. }
  106. function ensureNumber(n) {
  107. return (typeof n === 'number' && !isNaN(n));
  108. }
  109. self.initialize = function() {
  110. if (self.initialized) return true;
  111. self.error = '';
  112. self.d = (typeof target === 'string') ? document.getElementById(target) : target;
  113. if (!self.d) {
  114. self.error = '[CountUp] target is null or undefined'
  115. return false;
  116. }
  117. self.startVal = Number(startVal);
  118. self.endVal = Number(endVal);
  119. // error checks
  120. if (ensureNumber(self.startVal) && ensureNumber(self.endVal)) {
  121. self.decimals = Math.max(0, decimals || 0);
  122. self.dec = Math.pow(10, self.decimals);
  123. self.duration = Number(duration) * 1000 || 2000;
  124. self.countDown = (self.startVal > self.endVal);
  125. self.frameVal = self.startVal;
  126. self.initialized = true;
  127. return true;
  128. }
  129. else {
  130. self.error = '[CountUp] startVal ('+startVal+') or endVal ('+endVal+') is not a number';
  131. return false;
  132. }
  133. };
  134. // Print value to target
  135. self.printValue = function(value) {
  136. var result = self.options.formattingFn(value);
  137. if (self.d.tagName === 'INPUT') {
  138. this.d.value = result;
  139. }
  140. else if (self.d.tagName === 'text' || self.d.tagName === 'tspan') {
  141. this.d.textContent = result;
  142. }
  143. else {
  144. this.d.innerHTML = result;
  145. }
  146. };
  147. self.count = function(timestamp) {
  148. if (!self.startTime) { self.startTime = timestamp; }
  149. self.timestamp = timestamp;
  150. var progress = timestamp - self.startTime;
  151. self.remaining = self.duration - progress;
  152. // to ease or not to ease
  153. if (self.options.useEasing) {
  154. if (self.countDown) {
  155. self.frameVal = self.startVal - self.options.easingFn(progress, 0, self.startVal - self.endVal, self.duration);
  156. } else {
  157. self.frameVal = self.options.easingFn(progress, self.startVal, self.endVal - self.startVal, self.duration);
  158. }
  159. } else {
  160. if (self.countDown) {
  161. self.frameVal = self.startVal - ((self.startVal - self.endVal) * (progress / self.duration));
  162. } else {
  163. self.frameVal = self.startVal + (self.endVal - self.startVal) * (progress / self.duration);
  164. }
  165. }
  166. // don't go past endVal since progress can exceed duration in the last frame
  167. if (self.countDown) {
  168. self.frameVal = (self.frameVal < self.endVal) ? self.endVal : self.frameVal;
  169. } else {
  170. self.frameVal = (self.frameVal > self.endVal) ? self.endVal : self.frameVal;
  171. }
  172. // decimal
  173. self.frameVal = Math.round(self.frameVal*self.dec)/self.dec;
  174. // format and print value
  175. self.printValue(self.frameVal);
  176. // whether to continue
  177. if (progress < self.duration) {
  178. self.rAF = requestAnimationFrame(self.count);
  179. } else {
  180. if (self.callback) self.callback();
  181. }
  182. };
  183. // start your animation
  184. self.start = function(callback) {
  185. if (!self.initialize()) return;
  186. self.callback = callback;
  187. self.rAF = requestAnimationFrame(self.count);
  188. };
  189. // toggles pause/resume animation
  190. self.pauseResume = function() {
  191. if (!self.paused) {
  192. self.paused = true;
  193. cancelAnimationFrame(self.rAF);
  194. } else {
  195. self.paused = false;
  196. delete self.startTime;
  197. self.duration = self.remaining;
  198. self.startVal = self.frameVal;
  199. requestAnimationFrame(self.count);
  200. }
  201. };
  202. // reset to startVal so animation can be run again
  203. self.reset = function() {
  204. self.paused = false;
  205. delete self.startTime;
  206. self.initialized = false;
  207. if (self.initialize()) {
  208. cancelAnimationFrame(self.rAF);
  209. self.printValue(self.startVal);
  210. }
  211. };
  212. // pass a new endVal and start animation
  213. self.update = function (newEndVal) {
  214. if (!self.initialize()) return;
  215. newEndVal = Number(newEndVal);
  216. if (!ensureNumber(newEndVal)) {
  217. self.error = '[CountUp] update() - new endVal is not a number: '+newEndVal;
  218. return;
  219. }
  220. self.error = '';
  221. if (newEndVal === self.frameVal) return;
  222. cancelAnimationFrame(self.rAF);
  223. self.paused = false;
  224. delete self.startTime;
  225. self.startVal = self.frameVal;
  226. self.endVal = newEndVal;
  227. self.countDown = (self.startVal > self.endVal);
  228. self.rAF = requestAnimationFrame(self.count);
  229. };
  230. // format startVal on initialization
  231. if (self.initialize()) self.printValue(self.startVal);
  232. };
  233. return CountUp;
  234. }));