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