No Description

odometer.js 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. (function() {
  2. var COUNT_FRAMERATE, COUNT_MS_PER_FRAME, DIGIT_FORMAT, DIGIT_HTML, DIGIT_SPEEDBOOST, DURATION, FORMAT_MARK_HTML, FORMAT_PARSER, FRAMERATE, FRAMES_PER_VALUE, MS_PER_FRAME, MutationObserver, Odometer, RIBBON_HTML, TRANSITION_END_EVENTS, TRANSITION_SUPPORT, VALUE_HTML, addClass, createFromHTML, fractionalPart, now, removeClass, requestAnimationFrame, round, transitionCheckStyles, trigger, truncate, wrapJQuery, _jQueryWrapped, _old, _ref, _ref1,
  3. __slice = [].slice;
  4. VALUE_HTML = '<span class="odometer-value"></span>';
  5. RIBBON_HTML = '<span class="odometer-ribbon"><span class="odometer-ribbon-inner">' + VALUE_HTML + '</span></span>';
  6. DIGIT_HTML = '<span class="odometer-digit"><span class="odometer-digit-spacer">8</span><span class="odometer-digit-inner">' + RIBBON_HTML + '</span></span>';
  7. FORMAT_MARK_HTML = '<span class="odometer-formatting-mark"></span>';
  8. DIGIT_FORMAT = '(,ddd).dd';
  9. FORMAT_PARSER = /^\(?([^)]*)\)?(?:(.)(d+))?$/;
  10. FRAMERATE = 30;
  11. DURATION = 2000;
  12. COUNT_FRAMERATE = 20;
  13. FRAMES_PER_VALUE = 2;
  14. DIGIT_SPEEDBOOST = .5;
  15. MS_PER_FRAME = 1000 / FRAMERATE;
  16. COUNT_MS_PER_FRAME = 1000 / COUNT_FRAMERATE;
  17. TRANSITION_END_EVENTS = 'transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd';
  18. transitionCheckStyles = document.createElement('div').style;
  19. TRANSITION_SUPPORT = (transitionCheckStyles.transition != null) || (transitionCheckStyles.webkitTransition != null) || (transitionCheckStyles.mozTransition != null) || (transitionCheckStyles.oTransition != null);
  20. requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  21. MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  22. createFromHTML = function(html) {
  23. var el;
  24. el = document.createElement('div');
  25. el.innerHTML = html;
  26. return el.children[0];
  27. };
  28. removeClass = function(el, name) {
  29. return el.className = el.className.replace(new RegExp("(^| )" + (name.split(' ').join('|')) + "( |$)", 'gi'), ' ');
  30. };
  31. addClass = function(el, name) {
  32. removeClass(el, name);
  33. return el.className += " " + name;
  34. };
  35. trigger = function(el, name) {
  36. var evt;
  37. if (document.createEvent != null) {
  38. evt = document.createEvent('HTMLEvents');
  39. evt.initEvent(name, true, true);
  40. return el.dispatchEvent(evt);
  41. }
  42. };
  43. now = function() {
  44. var _ref, _ref1;
  45. return (_ref = (_ref1 = window.performance) != null ? typeof _ref1.now === "function" ? _ref1.now() : void 0 : void 0) != null ? _ref : +(new Date);
  46. };
  47. round = function(val, precision) {
  48. if (precision == null) {
  49. precision = 0;
  50. }
  51. if (!precision) {
  52. return Math.round(val);
  53. }
  54. val *= Math.pow(10, precision);
  55. val += 0.5;
  56. val = Math.floor(val);
  57. return val /= Math.pow(10, precision);
  58. };
  59. truncate = function(val) {
  60. if (val < 0) {
  61. return Math.ceil(val);
  62. } else {
  63. return Math.floor(val);
  64. }
  65. };
  66. fractionalPart = function(val) {
  67. return val - round(val);
  68. };
  69. _jQueryWrapped = false;
  70. (wrapJQuery = function() {
  71. var property, _i, _len, _ref, _results;
  72. if (_jQueryWrapped) {
  73. return;
  74. }
  75. if (window.jQuery != null) {
  76. _jQueryWrapped = true;
  77. _ref = ['html', 'text'];
  78. _results = [];
  79. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  80. property = _ref[_i];
  81. _results.push((function(property) {
  82. var old;
  83. old = window.jQuery.fn[property];
  84. return window.jQuery.fn[property] = function(val) {
  85. var _ref1;
  86. if ((val == null) || (((_ref1 = this[0]) != null ? _ref1.odometer : void 0) == null)) {
  87. return old.apply(this, arguments);
  88. }
  89. return this[0].odometer.update(val);
  90. };
  91. })(property));
  92. }
  93. return _results;
  94. }
  95. })();
  96. setTimeout(wrapJQuery, 0);
  97. Odometer = (function() {
  98. function Odometer(options) {
  99. var e, k, property, v, _base, _i, _len, _ref, _ref1, _ref2,
  100. _this = this;
  101. this.options = options;
  102. this.el = this.options.el;
  103. if (this.el.odometer != null) {
  104. return this.el.odometer;
  105. }
  106. this.el.odometer = this;
  107. _ref = Odometer.options;
  108. for (k in _ref) {
  109. v = _ref[k];
  110. if (this.options[k] == null) {
  111. this.options[k] = v;
  112. }
  113. }
  114. if ((_base = this.options).duration == null) {
  115. _base.duration = DURATION;
  116. }
  117. this.MAX_VALUES = ((this.options.duration / MS_PER_FRAME) / FRAMES_PER_VALUE) | 0;
  118. this.resetFormat();
  119. this.value = this.cleanValue((_ref1 = this.options.value) != null ? _ref1 : '');
  120. this.renderInside();
  121. this.render();
  122. try {
  123. _ref2 = ['innerHTML', 'innerText', 'textContent'];
  124. for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
  125. property = _ref2[_i];
  126. if (this.el[property] != null) {
  127. (function(property) {
  128. return Object.defineProperty(_this.el, property, {
  129. get: function() {
  130. var _ref3;
  131. if (property === 'innerHTML') {
  132. return _this.inside.outerHTML;
  133. } else {
  134. return (_ref3 = _this.inside.innerText) != null ? _ref3 : _this.inside.textContent;
  135. }
  136. },
  137. set: function(val) {
  138. return _this.update(val);
  139. }
  140. });
  141. })(property);
  142. }
  143. }
  144. } catch (_error) {
  145. e = _error;
  146. this.watchForMutations();
  147. }
  148. this;
  149. }
  150. Odometer.prototype.renderInside = function() {
  151. this.inside = document.createElement('div');
  152. this.inside.className = 'odometer-inside';
  153. this.el.innerHTML = '';
  154. return this.el.appendChild(this.inside);
  155. };
  156. Odometer.prototype.watchForMutations = function() {
  157. var e,
  158. _this = this;
  159. if (MutationObserver == null) {
  160. return;
  161. }
  162. try {
  163. if (this.observer == null) {
  164. this.observer = new MutationObserver(function(mutations) {
  165. var newVal;
  166. newVal = _this.el.innerText;
  167. _this.renderInside();
  168. _this.render(_this.value);
  169. return _this.update(newVal);
  170. });
  171. }
  172. this.watchMutations = true;
  173. return this.startWatchingMutations();
  174. } catch (_error) {
  175. e = _error;
  176. }
  177. };
  178. Odometer.prototype.startWatchingMutations = function() {
  179. if (this.watchMutations) {
  180. return this.observer.observe(this.el, {
  181. childList: true
  182. });
  183. }
  184. };
  185. Odometer.prototype.stopWatchingMutations = function() {
  186. var _ref;
  187. return (_ref = this.observer) != null ? _ref.disconnect() : void 0;
  188. };
  189. Odometer.prototype.cleanValue = function(val) {
  190. var _ref;
  191. if (typeof val === 'string') {
  192. val = val.replace((_ref = this.format.radix) != null ? _ref : '.', '<radix>');
  193. val = val.replace(/[.,]/g, '');
  194. val = val.replace('<radix>', '.');
  195. val = parseFloat(val, 10) || 0;
  196. }
  197. return round(val, this.format.precision);
  198. };
  199. Odometer.prototype.bindTransitionEnd = function() {
  200. var event, renderEnqueued, _i, _len, _ref, _results,
  201. _this = this;
  202. if (this.transitionEndBound) {
  203. return;
  204. }
  205. this.transitionEndBound = true;
  206. renderEnqueued = false;
  207. _ref = TRANSITION_END_EVENTS.split(' ');
  208. _results = [];
  209. for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  210. event = _ref[_i];
  211. _results.push(this.el.addEventListener(event, function() {
  212. if (renderEnqueued) {
  213. return true;
  214. }
  215. renderEnqueued = true;
  216. setTimeout(function() {
  217. _this.render();
  218. renderEnqueued = false;
  219. return trigger(_this.el, 'odometerdone');
  220. }, 0);
  221. return true;
  222. }, false));
  223. }
  224. return _results;
  225. };
  226. Odometer.prototype.resetFormat = function() {
  227. var format, fractional, parsed, precision, radix, repeating, _ref, _ref1;
  228. format = (_ref = this.options.format) != null ? _ref : DIGIT_FORMAT;
  229. format || (format = 'd');
  230. parsed = FORMAT_PARSER.exec(format);
  231. if (!parsed) {
  232. throw new Error("Odometer: Unparsable digit format");
  233. }
  234. _ref1 = parsed.slice(1, 4), repeating = _ref1[0], radix = _ref1[1], fractional = _ref1[2];
  235. precision = (fractional != null ? fractional.length : void 0) || 0;
  236. return this.format = {
  237. repeating: repeating,
  238. radix: radix,
  239. precision: precision
  240. };
  241. };
  242. Odometer.prototype.render = function(value) {
  243. var classes, cls, digit, match, newClasses, theme, wholePart, _i, _j, _len, _len1, _ref;
  244. if (value == null) {
  245. value = this.value;
  246. }
  247. this.stopWatchingMutations();
  248. this.resetFormat();
  249. this.inside.innerHTML = '';
  250. theme = this.options.theme;
  251. classes = this.el.className.split(' ');
  252. newClasses = [];
  253. for (_i = 0, _len = classes.length; _i < _len; _i++) {
  254. cls = classes[_i];
  255. if (!cls.length) {
  256. continue;
  257. }
  258. if (match = /^odometer-theme-(.+)$/.exec(cls)) {
  259. theme = match[1];
  260. continue;
  261. }
  262. if (/^odometer(-|$)/.test(cls)) {
  263. continue;
  264. }
  265. newClasses.push(cls);
  266. }
  267. newClasses.push('odometer');
  268. if (!TRANSITION_SUPPORT) {
  269. newClasses.push('odometer-no-transitions');
  270. }
  271. if (theme) {
  272. newClasses.push("odometer-theme-" + theme);
  273. } else {
  274. newClasses.push("odometer-auto-theme");
  275. }
  276. this.el.className = newClasses.join(' ');
  277. this.ribbons = {};
  278. this.digits = [];
  279. wholePart = !this.format.precision || !fractionalPart(value) || false;
  280. _ref = value.toString().split('').reverse();
  281. for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
  282. digit = _ref[_j];
  283. if (digit === '.') {
  284. wholePart = true;
  285. }
  286. this.addDigit(digit, wholePart);
  287. }
  288. return this.startWatchingMutations();
  289. };
  290. Odometer.prototype.update = function(newValue) {
  291. var diff,
  292. _this = this;
  293. newValue = this.cleanValue(newValue);
  294. if (!(diff = newValue - this.value)) {
  295. return;
  296. }
  297. removeClass(this.el, 'odometer-animating-up odometer-animating-down odometer-animating');
  298. if (diff > 0) {
  299. addClass(this.el, 'odometer-animating-up');
  300. } else {
  301. addClass(this.el, 'odometer-animating-down');
  302. }
  303. this.stopWatchingMutations();
  304. this.animate(newValue);
  305. this.startWatchingMutations();
  306. setTimeout(function() {
  307. _this.el.offsetHeight;
  308. return addClass(_this.el, 'odometer-animating');
  309. }, 0);
  310. return this.value = newValue;
  311. };
  312. Odometer.prototype.renderDigit = function() {
  313. return createFromHTML(DIGIT_HTML);
  314. };
  315. Odometer.prototype.insertDigit = function(digit, before) {
  316. if (before != null) {
  317. return this.inside.insertBefore(digit, before);
  318. } else if (!this.inside.children.length) {
  319. return this.inside.appendChild(digit);
  320. } else {
  321. return this.inside.insertBefore(digit, this.inside.children[0]);
  322. }
  323. };
  324. Odometer.prototype.addSpacer = function(chr, before, extraClasses) {
  325. var spacer;
  326. spacer = createFromHTML(FORMAT_MARK_HTML);
  327. spacer.innerHTML = chr;
  328. if (extraClasses) {
  329. addClass(spacer, extraClasses);
  330. }
  331. return this.insertDigit(spacer, before);
  332. };
  333. Odometer.prototype.addDigit = function(value, repeating) {
  334. var chr, digit, resetted, _ref;
  335. if (repeating == null) {
  336. repeating = true;
  337. }
  338. if (value === '-') {
  339. return this.addSpacer(value, null, 'odometer-negation-mark');
  340. }
  341. if (value === '.') {
  342. return this.addSpacer((_ref = this.format.radix) != null ? _ref : '.', null, 'odometer-radix-mark');
  343. }
  344. if (repeating) {
  345. resetted = false;
  346. while (true) {
  347. if (!this.format.repeating.length) {
  348. if (resetted) {
  349. throw new Error("Bad odometer format without digits");
  350. }
  351. this.resetFormat();
  352. resetted = true;
  353. }
  354. chr = this.format.repeating[this.format.repeating.length - 1];
  355. this.format.repeating = this.format.repeating.substring(0, this.format.repeating.length - 1);
  356. if (chr === 'd') {
  357. break;
  358. }
  359. this.addSpacer(chr);
  360. }
  361. }
  362. digit = this.renderDigit();
  363. digit.querySelector('.odometer-value').innerHTML = value;
  364. this.digits.push(digit);
  365. return this.insertDigit(digit);
  366. };
  367. Odometer.prototype.animate = function(newValue) {
  368. if (!TRANSITION_SUPPORT || this.options.animation === 'count') {
  369. return this.animateCount(newValue);
  370. } else {
  371. return this.animateSlide(newValue);
  372. }
  373. };
  374. Odometer.prototype.animateCount = function(newValue) {
  375. var cur, diff, last, start, tick,
  376. _this = this;
  377. if (!(diff = +newValue - this.value)) {
  378. return;
  379. }
  380. start = last = now();
  381. cur = this.value;
  382. return (tick = function() {
  383. var delta, dist, fraction;
  384. if ((now() - start) > _this.options.duration) {
  385. _this.value = newValue;
  386. _this.render();
  387. trigger(_this.el, 'odometerdone');
  388. return;
  389. }
  390. delta = now() - last;
  391. if (delta > COUNT_MS_PER_FRAME) {
  392. last = now();
  393. fraction = delta / _this.options.duration;
  394. dist = diff * fraction;
  395. cur += dist;
  396. _this.render(Math.round(cur));
  397. }
  398. if (requestAnimationFrame != null) {
  399. return requestAnimationFrame(tick);
  400. } else {
  401. return setTimeout(tick, COUNT_MS_PER_FRAME);
  402. }
  403. })();
  404. };
  405. Odometer.prototype.getDigitCount = function() {
  406. var i, max, value, values, _i, _len;
  407. values = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
  408. for (i = _i = 0, _len = values.length; _i < _len; i = ++_i) {
  409. value = values[i];
  410. values[i] = Math.abs(value);
  411. }
  412. max = Math.max.apply(Math, values);
  413. return Math.ceil(Math.log(max + 1) / Math.log(10));
  414. };
  415. Odometer.prototype.getFractionalDigitCount = function() {
  416. var i, parser, parts, value, values, _i, _len;
  417. values = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
  418. parser = /^\-?\d*\.(\d*?)0*$/;
  419. for (i = _i = 0, _len = values.length; _i < _len; i = ++_i) {
  420. value = values[i];
  421. values[i] = value.toString();
  422. parts = parser.exec(values[i]);
  423. if (parts == null) {
  424. values[i] = 0;
  425. } else {
  426. values[i] = parts[1].length;
  427. }
  428. }
  429. return Math.max.apply(Math, values);
  430. };
  431. Odometer.prototype.resetDigits = function() {
  432. this.digits = [];
  433. this.ribbons = [];
  434. this.inside.innerHTML = '';
  435. return this.resetFormat();
  436. };
  437. Odometer.prototype.animateSlide = function(newValue) {
  438. var boosted, cur, diff, digitCount, digits, dist, end, fractionalCount, frame, frames, i, incr, j, mark, numEl, oldValue, start, _base, _i, _j, _k, _l, _len, _len1, _len2, _m, _ref, _results;
  439. oldValue = this.value;
  440. fractionalCount = this.getFractionalDigitCount(oldValue, newValue);
  441. if (fractionalCount) {
  442. newValue = newValue * Math.pow(10, fractionalCount);
  443. oldValue = oldValue * Math.pow(10, fractionalCount);
  444. }
  445. if (!(diff = newValue - oldValue)) {
  446. return;
  447. }
  448. this.bindTransitionEnd();
  449. digitCount = this.getDigitCount(oldValue, newValue);
  450. digits = [];
  451. boosted = 0;
  452. for (i = _i = 0; 0 <= digitCount ? _i < digitCount : _i > digitCount; i = 0 <= digitCount ? ++_i : --_i) {
  453. start = truncate(oldValue / Math.pow(10, digitCount - i - 1));
  454. end = truncate(newValue / Math.pow(10, digitCount - i - 1));
  455. dist = end - start;
  456. if (Math.abs(dist) > this.MAX_VALUES) {
  457. frames = [];
  458. incr = dist / (this.MAX_VALUES + this.MAX_VALUES * boosted * DIGIT_SPEEDBOOST);
  459. cur = start;
  460. while ((dist > 0 && cur < end) || (dist < 0 && cur > end)) {
  461. frames.push(Math.round(cur));
  462. cur += incr;
  463. }
  464. if (frames[frames.length - 1] !== end) {
  465. frames.push(end);
  466. }
  467. boosted++;
  468. } else {
  469. frames = (function() {
  470. _results = [];
  471. for (var _j = start; start <= end ? _j <= end : _j >= end; start <= end ? _j++ : _j--){ _results.push(_j); }
  472. return _results;
  473. }).apply(this);
  474. }
  475. for (i = _k = 0, _len = frames.length; _k < _len; i = ++_k) {
  476. frame = frames[i];
  477. frames[i] = Math.abs(frame % 10);
  478. }
  479. digits.push(frames);
  480. }
  481. this.resetDigits();
  482. _ref = digits.reverse();
  483. for (i = _l = 0, _len1 = _ref.length; _l < _len1; i = ++_l) {
  484. frames = _ref[i];
  485. if (!this.digits[i]) {
  486. this.addDigit(' ', i >= fractionalCount);
  487. }
  488. if ((_base = this.ribbons)[i] == null) {
  489. _base[i] = this.digits[i].querySelector('.odometer-ribbon-inner');
  490. }
  491. this.ribbons[i].innerHTML = '';
  492. if (diff < 0) {
  493. frames = frames.reverse();
  494. }
  495. for (j = _m = 0, _len2 = frames.length; _m < _len2; j = ++_m) {
  496. frame = frames[j];
  497. numEl = document.createElement('div');
  498. numEl.className = 'odometer-value';
  499. numEl.innerHTML = frame;
  500. this.ribbons[i].appendChild(numEl);
  501. if (j === frames.length - 1) {
  502. addClass(numEl, 'odometer-last-value');
  503. }
  504. if (j === 0) {
  505. addClass(numEl, 'odometer-first-value');
  506. }
  507. }
  508. }
  509. if (start < 0) {
  510. this.addDigit('-');
  511. }
  512. mark = this.inside.querySelector('.odometer-radix-mark');
  513. if (mark != null) {
  514. mark.parent.removeChild(mark);
  515. }
  516. if (fractionalCount) {
  517. return this.addSpacer(this.format.radix, this.digits[fractionalCount - 1], 'odometer-radix-mark');
  518. }
  519. };
  520. return Odometer;
  521. })();
  522. Odometer.options = (_ref = window.odometerOptions) != null ? _ref : {};
  523. setTimeout(function() {
  524. var k, v, _base, _ref1, _results;
  525. if (window.odometerOptions) {
  526. _ref1 = window.odometerOptions;
  527. _results = [];
  528. for (k in _ref1) {
  529. v = _ref1[k];
  530. _results.push((_base = Odometer.options)[k] != null ? (_base = Odometer.options)[k] : _base[k] = v);
  531. }
  532. return _results;
  533. }
  534. }, 0);
  535. Odometer.init = function() {
  536. var el, elements, _i, _len, _ref1, _results;
  537. if (document.querySelectorAll == null) {
  538. return;
  539. }
  540. elements = document.querySelectorAll(Odometer.options.selector || '.odometer');
  541. _results = [];
  542. for (_i = 0, _len = elements.length; _i < _len; _i++) {
  543. el = elements[_i];
  544. _results.push(el.odometer = new Odometer({
  545. el: el,
  546. value: (_ref1 = el.innerText) != null ? _ref1 : el.textContent
  547. }));
  548. }
  549. return _results;
  550. };
  551. if ((((_ref1 = document.documentElement) != null ? _ref1.doScroll : void 0) != null) && (document.createEventObject != null)) {
  552. _old = document.onreadystatechange;
  553. document.onreadystatechange = function() {
  554. if (document.readyState === 'complete' && Odometer.options.auto !== false) {
  555. Odometer.init();
  556. }
  557. return _old != null ? _old.apply(this, arguments) : void 0;
  558. };
  559. } else {
  560. document.addEventListener('DOMContentLoaded', function() {
  561. if (Odometer.options.auto !== false) {
  562. return Odometer.init();
  563. }
  564. }, false);
  565. }
  566. if (typeof define === 'function' && define.amd) {
  567. define(['jquery'], function() {
  568. return Odometer;
  569. });
  570. } else if (typeof exports === !'undefined') {
  571. module.exports = Odometer;
  572. } else {
  573. window.Odometer = Odometer;
  574. }
  575. }).call(this);