/* * jQuery Flip Counter v1.2 by Brolly.ca - http://bloggingsquared.com/jquery/flipcounter/ * * Uses valid markup and an image sprite to render an analogue clock / odometer effect. * Clock image is easily customizable, default options can be easily overriden, can be easily animated and extended with jQuery.easing plugin, gracefully degrades if Javascript is not available. * * TERMS OF USE - jQuery Flip Counter * * Open source under the BSD License. * * Copyright (C) 2010 Dan Imbrogno and Brolly Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the author nor the names of contributors may be used to endorse * or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * */ (function ($) { $.fn.flipCounter = function (method) { // Public Variables var obj = false; // reference to DOM object var defaults = { number: 0, // default number is 0, numIntegralDigits: 1, // default number of places left of . is 1 numFractionalDigits: 0, // default number of places right of . is 0 digitClass: 'counter-digit', // default class for the counter digits counterFieldName: 'counter-value',// default name for the counter hidden field digitHeight: 40, // default height of each digit in sprite image digitWidth: 30, // default width of each digit in sprite image imagePath: 'img/flipCounter-medium.png',// default path of sprite image relative to html document easing: false,// default easing function, this can be overridden by jQuery.easing methods (i.e. jQuery.easing.easeOutCubic) duration: 10000, // default duration of animations onAnimationStarted: false, // callback for animation starting onAnimationStopped: false,// callback for animation stopped, onAnimationPaused: false, // callback for animation paused onAnimationResumed: false,// callback for animation resumed formatNumberOptions: false }; // Public Methods var methods = { // Initialization init: function (options) { return this.each( function () { // Set reference to this DOM object obj = $(this); // new options override defaults var new_options = $.extend(defaults, options); var old_options = obj.data('flipCounter'); // new options override previous initializiation options options = $.extend(old_options, new_options); obj.data('flipCounter', options); // if number isn't set then try and get it from the hidden field if (options.number === false || options.number == 0) { (_getCounterValue() !== false) ? options.number = _getCounterValue() : options.number = 0; _setOption('number', options.number); } // Default Options _setOption('animating', false); _setOption('start_time', false); // Bind Actions obj.bind('startAnimation', function (event, options) { _startAnimation(options); }); obj.bind('pauseAnimation', function (event) { _pauseAnimation(); }); obj.bind('resumeAnimation', function (event) { _resumeAnimation(); }); obj.bind('stopAnimation', function (event) { _stopAnimation(); }); // Remove line breaks which cause rendering errors obj.htmlClean(); // Render counter _renderCounter(); } ) }, // Render a number in the counter renderCounter: function (value) { return this.each( function () { obj = $(this); if (!_isInitialized()) $(this).flipCounter(); _setOption('number', value); _renderCounter(); } ); }, // Render a number in the counter (synonym for above) setNumber: function (value) { return this.each( function () { obj = $(this); if (!_isInitialized()) $(this).flipCounter(); _setOption('number', value); _renderCounter(); } ); }, // Get the number currently rendered getNumber: function () { var val = false; this.each( val = function () { obj = $(this); if (!_isInitialized()) $(this).flipCounter(); val = _getOption('number'); return val; } ); return val; }, // Start animation or resume paused animation startAnimation: function (options) { return this.each( function () { obj = $(this); if (!_isInitialized()) $(this).flipCounter(); obj.trigger('startAnimation', options); } ); }, // Stop animation stopAnimation: function () { return this.each( function () { obj = $(this); if (!_isInitialized()) $(this).flipCounter(); obj.trigger('stopAnimation'); } ); }, // Pause animation pauseAnimation: function () { return this.each( function () { obj = $(this); if (!_isInitialized()) $(this).flipCounter(); obj.trigger('pauseAnimation'); } ); }, // Resume animation resumeAnimation: function () { return this.each( function () { obj = $(this); if (!_isInitialized()) $(this).flipCounter(); obj.trigger('resumeAnimation'); } ); } }; // Call public methods if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === 'object' || !method) { return methods.init.apply(this, arguments); } else { $.error('Method ' + method + ' does not exist on jQuery.flipCounter'); } // Private Functions function _isInitialized() { var data = obj.data('flipCounter'); if (typeof data == 'undefined') return false return true; } // Get an option function _getOption(option) { var data = obj.data('flipCounter'); var value = data[option]; if (typeof value !== 'undefined') { return value; } return false; } // Set an option function _setOption(option, value) { var data = obj.data('flipCounter'); data[option] = value; obj.data('flipCounter', data); } // Setup the counter function _setupCounter() { // If object doesn't have a hidden field then create one if (obj.children('[name="' + _getOption('counterFieldName') + '"]').length < 1) { obj.append(''); } // Add or remove enough digits to fit number var digits_length = _getDigitsLength(); var num_digits_needed = _getNumberFormatted().length; if (num_digits_needed > digits_length) { for (i = 0; i < (num_digits_needed - digits_length); i++) { var digit_element = $(''); obj.prepend(digit_element); } } else if (num_digits_needed < digits_length) { for (i = 0; i < (digits_length - num_digits_needed); i++) { obj.children('.' + _getOption('digitClass')).first().remove(); } } // Add the invisible span if it doesn't already exist obj.find('.' + _getOption('digitClass')).each( function () { if (0 == $(this).find('span').length) { $(this).append('0'); } } ); } // Render the counter function _renderCounter() { _setupCounter(); var number = _getNumberFormatted(); var digits = _getDigits(); var pos = 0; $.each(digits, function (index, value) { digit = number.toString().charAt(pos); $(this).attr('style', _getDigitStyle(digit)); $(this).find('span').text(digit.replace(' ', ' ').toString()); // replace empty space with   to prevent rendering bug pos++ }); _setCounterValue(); } // get a collection of the objects digit DOM elements function _getDigits() { return obj.children('.' + _getOption('digitClass')); } // get the current number of digit DOM elements function _getDigitsLength() { return _getDigits().length; } // get the value stored in the counter field function _getCounterValue() { var val = parseFloat(obj.children('[name="' + _getOption('counterFieldName') + '"]').val()); if (val == val == false) return false; // test for NaN return val; } // update the counter field with the current number function _setCounterValue() { obj.children('[name="' + _getOption('counterFieldName') + '"]').val(_getOption('number')); } // format number as a string according to given options function _getNumberFormatted() { var number = _getOption('number'); // check is numeric if (typeof number !== 'number') { $.error('Attempting to render non-numeric value.'); return '0'; } var str_number = ''; // Number formatter plugin is being used if (_getOption('formatNumberOptions')) { if ($.formatNumber) { str_number = $.formatNumber(number, _getOption('formatNumberOptions')); } else { $.error('The numberformatter jQuery plugin is not loaded. This plugin is required to use the formatNumberOptions setting.'); } } else { // if greater than zero add leading zeros if necessary if (number >= 0) { var num_integral_digits = _getOption('numIntegralDigits'); var num_extra_zeros = num_integral_digits - number.toFixed().toString().length; for (var i = 0; i < num_extra_zeros; i++) { str_number += '0'; } str_number += number.toFixed(_getOption('numFractionalDigits')); // if less than zero remove leading zeros and add minus sign } else { str_number = '-' + Math.abs(number.toFixed(_getOption('numFractionalDigits'))); } } return str_number; } // Get CSS background image positiong function _getDigitStyle(character) { var style = "height:" + _getOption('digitHeight') + "px; width:" + _getOption('digitWidth') + "px; display:inline-block; background-image:url('" + _getOption('imagePath') + "'); background-repeat:no-repeat; "; var bg_pos = new Array(); bg_pos['1'] = _getOption('digitWidth') * 0; bg_pos['2'] = _getOption('digitWidth') * -1; bg_pos['3'] = _getOption('digitWidth') * -2; bg_pos['4'] = _getOption('digitWidth') * -3; bg_pos['5'] = _getOption('digitWidth') * -4; bg_pos['6'] = _getOption('digitWidth') * -5; bg_pos['7'] = _getOption('digitWidth') * -6; bg_pos['8'] = _getOption('digitWidth') * -7; bg_pos['9'] = _getOption('digitWidth') * -8; bg_pos['0'] = _getOption('digitWidth') * -9; bg_pos['.'] = _getOption('digitWidth') * -10; bg_pos['-'] = _getOption('digitWidth') * -11; bg_pos[','] = _getOption('digitWidth') * -12; bg_pos[' '] = _getOption('digitWidth') * -13; if( character in bg_pos) { return style + 'background-position: ' + bg_pos[character] + 'px 0px;' } return style; } // Start the animation function _startAnimation(options) { if (true == _getOption('animating')) _stopAnimation(); if (typeof options !== 'undefined') { options = $.extend(obj.data('flipCounter'), options); obj.data('flipCounter', options); } else { options = obj.data('flipCounter'); } if (false == _getOption('start_time')) { _setOption('start_time', new Date().getTime()); } if (false == _getOption('time')) { _setOption('time', 0); } if (false == _getOption('elapsed')) { _setOption('elapsed', '0.0'); } if (false == _getOption('start_number')) { _setOption('start_number', _getOption('number')); if (false == _getOption('start_number')) { _setOption('start_number', 0); } } _doAnimation(); var onAnimationStarted = _getOption('onAnimationStarted'); if (typeof onAnimationStarted == 'function') onAnimationStarted.call(obj, obj); } // Do animation step function _doAnimation() { var start_time = _getOption('start_time'); var time = _getOption('time'); var elapsed = _getOption('elapsed'); var start_number = _getOption('start_number'); var number_change = _getOption('end_number') - _getOption('start_number'); if (number_change == 0) return false; var duration = _getOption('duration'); var easing = _getOption('easing'); _setOption('animating', true); function animation_step() { time += 10; elapsed = Math.floor(time / 10) / 10; if (Math.round(elapsed) == elapsed) { elapsed += '.0'; } _setOption('elapsed', elapsed); var diff = (new Date().getTime() - start_time) - time; var new_num = 0; if(typeof easing == 'function') { new_num = easing.apply(obj, [false, time, start_number, number_change, duration]); } else { new_num = _noEasing(false, time, start_number, number_change, duration); } _setOption('number', new_num); _setOption('time', time); _renderCounter(); if (time < duration) { _setOption('interval', window.setTimeout(animation_step, (10 - diff))); } else { _stopAnimation(); } } window.setTimeout(animation_step, 10); } // Stop animation function _stopAnimation() { if (false == _getOption('animating')) return false; clearTimeout(_getOption('interval')); _setOption('start_time', false); _setOption('start_number', false); _setOption('end_number', false); _setOption('time', 0); _setOption('animating', false); _setOption('paused', false); var onAnimationStopped = _getOption('onAnimationStopped'); if (typeof onAnimationStopped == 'function') onAnimationStopped.call(obj, obj); } // Pause animation function _pauseAnimation() { if (false == _getOption('animating') || true == _getOption('paused')) return false; clearTimeout(_getOption('interval')); _setOption('paused', true); var onAnimationPaused = _getOption('onAnimationPaused'); if (typeof onAnimationPaused == 'function') onAnimationPaused.call(obj, obj); } // Resume animation function _resumeAnimation() { if (false == _getOption('animating') || false == _getOption('paused')) return false; _setOption('paused', false); _doAnimation(); var onAnimationResumed = _getOption('onAnimationResumed'); if (typeof onAnimationResumed == 'function') onAnimationResumed.call(obj, obj); } // Default linear interpolation function _noEasing(x, t, b, c, d) { return t / d * c + b; } } })(jQuery); // Used to remove white space in counter that causes rendering bugs jQuery.fn.htmlClean = function () { this.contents().filter(function () { if (this.nodeType != 3) { $(this).htmlClean(); return false; } else { return !/\S/.test(this.nodeValue); } }).remove(); }