240 lines
10 KiB
JavaScript
240 lines
10 KiB
JavaScript
|
(function (root, factory) {
|
||
|
if (typeof define === 'function' && define.amd) {
|
||
|
define(factory);
|
||
|
} else if (typeof exports === 'object') {
|
||
|
module.exports = factory(require, exports, module);
|
||
|
} else {
|
||
|
root.CountUp = factory();
|
||
|
}
|
||
|
}(this, function (require, exports, module) {
|
||
|
/*
|
||
|
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);
|
||
|
};
|
||
|
return CountUp;
|
||
|
}));
|