2 * Apple-Style Flip Counter
3 * Version 0.5.3 - May 7, 2011
5 * Copyright (c) 2010 Chris Nanney
6 * http://cnanney.com/journal/code/apple-style-counter-revisited/
9 * http://www.opensource.org/licenses/mit-license.php
12 var flipCounter = function(d, options){
26 var doc = window.document,
27 divId = typeof d !== 'undefined' && d !== '' ? d : 'flip-counter',
28 div = doc.getElementById(divId);
31 for (var opt in defaults) {
32 o[opt] = (opt in options) ? options[opt] : defaults[opt];
35 var digitsOld = [], digitsNew = [], subStart, subEnd, x, y, nextCount = null, newDigit, newComma,
43 * Sets the value of the counter and animates the digits to new value.
45 * Example: myCounter.setValue(500); would set the value of the counter to 500,
46 * no matter what value it was previously.
51 this.setValue = function(n){
62 * Sets the increment for the counter. Does NOT animate digits.
64 this.setIncrement = function(n){
65 o.inc = isNumber(n) ? n : defaults.inc;
70 * Sets the pace of the counter. Only affects counter when auto == true.
73 * New pace for counter in milliseconds
75 this.setPace = function(n){
76 o.pace = isNumber(n) ? n : defaults.pace;
81 * Sets counter to auto-incrememnt (true) or not (false).
84 * Should counter auto-increment, true or false
86 this.setAuto = function(a){
92 if (nextCount) clearNext();
99 * Increments counter by one animation based on set 'inc' value.
101 this.step = function(){
102 if (! o.auto) doCount();
107 * Adds a number to the counter value, not affecting the 'inc' or 'pace' of the counter.
110 * Number to add to counter value
112 this.add = function(n){
123 * Subtracts a number from the counter value, not affecting the 'inc' or 'pace' of the counter.
126 * Number to subtract from counter value
128 this.subtract = function(n){
145 * Increments counter to given value, animating by current pace and increment.
148 * Number to increment to
149 * @param {int} t (optional)
150 * Time duration in seconds - makes increment a 'smart' increment
151 * @param {int} p (optional)
152 * Desired pace for counter if 'smart' increment
154 this.incrementTo = function(n, t, p){
155 if (nextCount) clearNext();
158 if (typeof t != 'undefined'){
159 var time = isNumber(t) ? t * 1000 : 10000,
160 pace = typeof p != 'undefined' && isNumber(p) ? p : o.pace,
161 diff = typeof n != 'undefined' && isNumber(n) ? n - o.value : 0,
162 cycles, inc, check, i = 0;
165 // Initial best guess
166 pace = (time / diff > pace) ? Math.round((time / diff) / 10) * 10 : pace;
167 cycles = Math.floor(time / pace);
168 inc = Math.floor(diff / cycles);
170 check = checkSmartValues(diff, cycles, inc, pace, time);
173 while (check.result === false && i < 100){
175 cycles = Math.floor(time / pace);
176 inc = Math.floor(diff / cycles);
178 check = checkSmartValues(diff, cycles, inc, pace, time);
183 // Could not find optimal settings, use best found so far
188 // Optimal settings found, use those
193 doIncrement(n, true, cycles);
205 * Gets current value of counter.
207 this.getValue = function(){
212 * Stops all running increments.
214 this.stop = function(){
215 if (nextCount) clearNext();
219 //---------------------------------------------------------------------------//
226 if (o.auto === true) nextCount = setTimeout(doCount, o.pace);
229 function doIncrement(n, s, c){
231 smart = (typeof s == 'undefined') ? false : s,
232 cycles = (typeof c == 'undefined') ? 1 : c;
234 if (smart === true) cycles--;
240 if (val + o.inc <= n && cycles != 0) val += o.inc
247 nextCount = setTimeout(function(){doIncrement(n, smart, cycles)}, o.pace);
252 function digitCheck(x,y){
253 digitsOld = splitToArray(x);
254 digitsNew = splitToArray(y);
256 xlen = digitsOld.length,
257 ylen = digitsNew.length;
261 addDigit(ylen - diff + 1, digitsNew[ylen - diff]);
268 removeDigit(xlen - diff);
272 for (var i = 0; i < xlen; i++){
273 if (digitsNew[i] != digitsOld[i]){
274 animateDigit(i, digitsOld[i], digitsNew[i]);
279 function animateDigit(n, oldDigit, newDigit){
280 var speed, step = 0, w, a,
282 '-' + o.fW + 'px -' + (oldDigit * o.tFH) + 'px',
283 (o.fW * -2) + 'px -' + (oldDigit * o.tFH) + 'px',
284 '0 -' + (newDigit * o.tFH) + 'px',
285 '-' + o.fW + 'px -' + (oldDigit * o.bFH + o.bOffset) + 'px',
286 (o.fW * -2) + 'px -' + (newDigit * o.bFH + o.bOffset) + 'px',
287 (o.fW * -3) + 'px -' + (newDigit * o.bFH + o.bOffset) + 'px',
288 '0 -' + (newDigit * o.bFH + o.bOffset) + 'px'
291 if (o.auto === true && o.pace <= 300){
313 // Cap on slowest animation can go
314 speed = (speed > 80) ? 80 : speed;
318 w = step < 3 ? 't' : 'b';
319 a = doc.getElementById(divId + "_" + w + "_d" + n);
320 if (a) a.style.backgroundPosition = bp[step];
322 if (step != 3) setTimeout(animate, speed);
330 // Creates array of digits for easier manipulation
331 function splitToArray(input){
332 return input.toString().split("").reverse();
336 function addDigit(len, digit){
337 var li = Number(len) - 1;
338 newDigit = doc.createElement("ul");
339 newDigit.className = 'cd';
340 newDigit.id = divId + '_d' + li;
341 newDigit.innerHTML = '<li class="t" id="' + divId + '_t_d' + li + '"></li><li class="b" id="' + divId + '_b_d' + li + '"></li>';
344 newComma = doc.createElement("ul");
345 newComma.className = 'cd';
346 newComma.innerHTML = '<li class="s"></li>';
347 div.insertBefore(newComma, div.firstChild);
350 div.insertBefore(newDigit, div.firstChild);
351 doc.getElementById(divId + "_t_d" + li).style.backgroundPosition = '0 -' + (digit * o.tFH) + 'px';
352 doc.getElementById(divId + "_b_d" + li).style.backgroundPosition = '0 -' + (digit * o.bFH + o.bOffset) + 'px';
356 function removeDigit(id){
357 var remove = doc.getElementById(divId + "_d" + id);
358 div.removeChild(remove);
360 // Check for leading comma
361 var first = div.firstChild.firstChild;
362 if ((" " + first.className + " ").indexOf(" s ") > -1 ){
363 remove = first.parentNode;
364 div.removeChild(remove);
368 // Sets the correct digits on load
369 function initialDigitCheck(init){
370 // Creates the right number of digits
371 var initial = init.toString(),
372 count = initial.length,
374 for (i = 0; i < count; i++){
375 newDigit = doc.createElement("ul");
376 newDigit.className = 'cd';
377 newDigit.id = divId + '_d' + i;
378 newDigit.innerHTML = newDigit.innerHTML = '<li class="t" id="' + divId + '_t_d' + i + '"></li><li class="b" id="' + divId + '_b_d' + i + '"></li>';
379 div.insertBefore(newDigit, div.firstChild);
380 if (bit != (count) && bit % 3 == 0){
381 newComma = doc.createElement("ul");
382 newComma.className = 'cd';
383 newComma.innerHTML = '<li class="s"></li>';
384 div.insertBefore(newComma, div.firstChild);
388 // Sets them to the right number
389 var digits = splitToArray(initial);
390 for (i = 0; i < count; i++){
391 doc.getElementById(divId + "_t_d" + i).style.backgroundPosition = '0 -' + (digits[i] * o.tFH) + 'px';
392 doc.getElementById(divId + "_b_d" + i).style.backgroundPosition = '0 -' + (digits[i] * o.bFH + o.bOffset) + 'px';
394 // Do first animation
395 if (o.auto === true) nextCount = setTimeout(doCount, o.pace);
398 // Checks values for smart increment and creates debug text
399 function checkSmartValues(diff, cycles, inc, pace, time){
400 var r = {result: true}, q;
401 // Test conditions, all must pass to continue:
402 // 1: Unrounded inc value needs to be at least 1
403 r.cond1 = (diff / cycles >= 1) ? true : false;
404 // 2: Don't want to overshoot the target number
405 r.cond2 = (cycles * inc <= diff) ? true : false;
406 // 3: Want to be within 10 of the target number
407 r.cond3 = (Math.abs(cycles * inc - diff) <= 10) ? true : false;
408 // 4: Total time should be within 100ms of target time.
409 r.cond4 = (Math.abs(cycles * pace - time) <= 100) ? true : false;
410 // 5: Calculated time should not be over target time
411 r.cond5 = (cycles * pace <= time) ? true : false;
413 // Keep track of 'good enough' values in case can't find best one within 100 loops
414 if (r.cond1 && r.cond2 && r.cond4 && r.cond5){
415 q = Math.abs(diff - (cycles * inc)) + Math.abs(cycles * pace - time);
416 if (best.q === null) best.q = q;
423 for (var i = 1; i <= 5; i++){
424 if (r['cond' + i] === false){
431 // http://stackoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric/1830844
432 function isNumber(n) {
433 return !isNaN(parseFloat(n)) && isFinite(n);
436 function clearNext(){
437 clearTimeout(nextCount);
442 initialDigitCheck(o.value);