| /* |
| |
| countUp.js |
| by @inorganik |
| |
| */ |
| |
| // target = id of html element or var of previously selected html element where counting occurs |
| // startVal = the value you want to begin at |
| // endVal = the value you want to arrive at |
| // decimals = number of decimal places, default 0 |
| // duration = duration of animation in seconds, default 2 |
| // options = optional object of options (see below) |
| |
| var CountUp = function(target, startVal, endVal, decimals, duration, options) { |
| |
| var self = this; |
| self.version = function () { return '1.9.3'; }; |
| |
| // default options |
| self.options = { |
| useEasing: true, // toggle easing |
| useGrouping: true, // 1,000,000 vs 1000000 |
| separator: ',', // character to use as a separator |
| decimal: '.', // character to use as a decimal |
| easingFn: easeOutExpo, // optional custom easing function, default is Robert Penner's easeOutExpo |
| formattingFn: formatNumber, // optional custom formatting function, default is formatNumber above |
| prefix: '', // optional text before the result |
| suffix: '', // optional text after the result |
| numerals: [] // optionally pass an array of custom numerals for 0-9 |
| }; |
| |
| // extend default options with passed options object |
| if (options && typeof options === 'object') { |
| for (var key in self.options) { |
| if (options.hasOwnProperty(key) && options[key] !== null) { |
| self.options[key] = options[key]; |
| } |
| } |
| } |
| |
| if (self.options.separator === '') { |
| self.options.useGrouping = false; |
| } |
| else { |
| // ensure the separator is a string (formatNumber assumes this) |
| self.options.separator = '' + self.options.separator; |
| } |
| |
| // make sure requestAnimationFrame and cancelAnimationFrame are defined |
| // polyfill for browsers without native support |
| // by Opera engineer Erik Möller |
| var lastTime = 0; |
| var vendors = ['webkit', 'moz', 'ms', 'o']; |
| for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { |
| window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; |
| window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; |
| } |
| if (!window.requestAnimationFrame) { |
| window.requestAnimationFrame = function(callback, element) { |
| var currTime = new Date().getTime(); |
| var timeToCall = Math.max(0, 16 - (currTime - lastTime)); |
| var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); |
| lastTime = currTime + timeToCall; |
| return id; |
| }; |
| } |
| if (!window.cancelAnimationFrame) { |
| window.cancelAnimationFrame = function(id) { |
| clearTimeout(id); |
| }; |
| } |
| |
| function formatNumber(num) { |
| var neg = (num < 0), |
| x, x1, x2, x3, i, len; |
| num = Math.abs(num).toFixed(self.decimals); |
| num += ''; |
| x = num.split('.'); |
| x1 = x[0]; |
| x2 = x.length > 1 ? self.options.decimal + x[1] : ''; |
| if (self.options.useGrouping) { |
| x3 = ''; |
| for (i = 0, len = x1.length; i < len; ++i) { |
| if (i !== 0 && ((i % 3) === 0)) { |
| x3 = self.options.separator + x3; |
| } |
| x3 = x1[len - i - 1] + x3; |
| } |
| x1 = x3; |
| } |
| // optional numeral substitution |
| if (self.options.numerals.length) { |
| x1 = x1.replace(/[0-9]/g, function(w) { |
| return self.options.numerals[+w]; |
| }) |
| x2 = x2.replace(/[0-9]/g, function(w) { |
| return self.options.numerals[+w]; |
| }) |
| } |
| return (neg ? '-' : '') + self.options.prefix + x1 + x2 + self.options.suffix; |
| } |
| // Robert Penner's easeOutExpo |
| function easeOutExpo(t, b, c, d) { |
| return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b; |
| } |
| function ensureNumber(n) { |
| return (typeof n === 'number' && !isNaN(n)); |
| } |
| |
| self.initialize = function() { |
| if (self.initialized) return true; |
| |
| self.error = ''; |
| self.d = (typeof target === 'string') ? document.getElementById(target) : target; |
| if (!self.d) { |
| self.error = '[CountUp] target is null or undefined' |
| return false; |
| } |
| self.startVal = Number(startVal); |
| self.endVal = Number(endVal); |
| // error checks |
| if (ensureNumber(self.startVal) && ensureNumber(self.endVal)) { |
| self.decimals = Math.max(0, decimals || 0); |
| self.dec = Math.pow(10, self.decimals); |
| self.duration = Number(duration) * 1000 || 2000; |
| self.countDown = (self.startVal > self.endVal); |
| self.frameVal = self.startVal; |
| self.initialized = true; |
| return true; |
| } |
| else { |
| self.error = '[CountUp] startVal ('+startVal+') or endVal ('+endVal+') is not a number'; |
| return false; |
| } |
| }; |
| |
| // Print value to target |
| self.printValue = function(value) { |
| var result = self.options.formattingFn(value); |
| |
| if (self.d.tagName === 'INPUT') { |
| this.d.value = result; |
| } |
| else if (self.d.tagName === 'text' || self.d.tagName === 'tspan') { |
| this.d.textContent = result; |
| } |
| else { |
| this.d.innerHTML = result; |
| } |
| }; |
| |
| self.count = function(timestamp) { |
| |
| if (!self.startTime) { self.startTime = timestamp; } |
| |
| self.timestamp = timestamp; |
| var progress = timestamp - self.startTime; |
| self.remaining = self.duration - progress; |
| |
| // to ease or not to ease |
| if (self.options.useEasing) { |
| if (self.countDown) { |
| self.frameVal = self.startVal - self.options.easingFn(progress, 0, self.startVal - self.endVal, self.duration); |
| } else { |
| self.frameVal = self.options.easingFn(progress, self.startVal, self.endVal - self.startVal, self.duration); |
| } |
| } else { |
| if (self.countDown) { |
| self.frameVal = self.startVal - ((self.startVal - self.endVal) * (progress / self.duration)); |
| } else { |
| self.frameVal = self.startVal + (self.endVal - self.startVal) * (progress / self.duration); |
| } |
| } |
| |
| // don't go past endVal since progress can exceed duration in the last frame |
| if (self.countDown) { |
| self.frameVal = (self.frameVal < self.endVal) ? self.endVal : self.frameVal; |
| } else { |
| self.frameVal = (self.frameVal > self.endVal) ? self.endVal : self.frameVal; |
| } |
| |
| // decimal |
| self.frameVal = Math.round(self.frameVal*self.dec)/self.dec; |
| |
| // format and print value |
| self.printValue(self.frameVal); |
| |
| // whether to continue |
| if (progress < self.duration) { |
| self.rAF = requestAnimationFrame(self.count); |
| } else { |
| if (self.callback) self.callback(); |
| } |
| }; |
| // start your animation |
| self.start = function(callback) { |
| if (!self.initialize()) return; |
| self.callback = callback; |
| self.rAF = requestAnimationFrame(self.count); |
| }; |
| // toggles pause/resume animation |
| self.pauseResume = function() { |
| if (!self.paused) { |
| self.paused = true; |
| cancelAnimationFrame(self.rAF); |
| } else { |
| self.paused = false; |
| delete self.startTime; |
| self.duration = self.remaining; |
| self.startVal = self.frameVal; |
| requestAnimationFrame(self.count); |
| } |
| }; |
| // reset to startVal so animation can be run again |
| self.reset = function() { |
| self.paused = false; |
| delete self.startTime; |
| self.initialized = false; |
| if (self.initialize()) { |
| cancelAnimationFrame(self.rAF); |
| self.printValue(self.startVal); |
| } |
| }; |
| // pass a new endVal and start animation |
| self.update = function (newEndVal) { |
| if (!self.initialize()) return; |
| newEndVal = Number(newEndVal); |
| if (!ensureNumber(newEndVal)) { |
| self.error = '[CountUp] update() - new endVal is not a number: '+newEndVal; |
| return; |
| } |
| self.error = ''; |
| if (newEndVal === self.frameVal) return; |
| cancelAnimationFrame(self.rAF); |
| self.paused = false; |
| delete self.startTime; |
| self.startVal = self.frameVal; |
| self.endVal = newEndVal; |
| self.countDown = (self.startVal > self.endVal); |
| self.rAF = requestAnimationFrame(self.count); |
| }; |
| |
| // format startVal on initialization |
| if (self.initialize()) self.printValue(self.startVal); |
| }; |