├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── README.md ├── dist ├── css │ └── jquery.countimator.wheel.css └── js │ ├── jquery.countimator.js │ ├── jquery.countimator.min.js │ ├── jquery.countimator.wheel.js │ └── jquery.countimator.wheel.min.js ├── package.json └── src ├── css └── jquery.countimator.wheel.css └── js ├── jquery.countimator.js └── jquery.countimator.wheel.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 28 | node_modules 29 | 30 | # Eclipse 31 | .project 32 | 33 | # GFM Preview 34 | .*.md.html 35 | 36 | # Site 37 | site -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "boss": true, 11 | "eqnull": true, 12 | "node": true, 13 | "globals": { 14 | "window": false, 15 | "document": false, 16 | "$": false, 17 | "jQuery": false, 18 | "google": false 19 | } 20 | } -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON('package.json'), 5 | copy: { 6 | dist: { 7 | expand: true, cwd: 'src/', src: ['**'], dest: 'dist/' 8 | }, 9 | site: { 10 | expand: true, cwd: 'dist/', src: ['**/*'], dest: 'site/' 11 | } 12 | }, 13 | jshint: { 14 | all: ["src/**/*.js"], 15 | options: { 16 | jshintrc: ".jshintrc" 17 | } 18 | }, 19 | uglify: { 20 | options: { 21 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n' 22 | }, 23 | dist: { 24 | files: { 25 | 'dist/js/jquery.countimator.min.js': [ 'dist/js/jquery.countimator.js'], 26 | 'dist/js/jquery.countimator.wheel.min.js': [ 'dist/js/jquery.countimator.wheel.js'] 27 | } 28 | } 29 | }, 30 | livemd: { 31 | options: { 32 | prefilter: function(string) { 33 | return string.replace(grunt.config().pkg && grunt.config().pkg.homepage && new RegExp("\\[.*\\]\\(" + grunt.config().pkg.homepage.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + "\\)", "gi"), ""); 34 | } 35 | }, 36 | site: { 37 | files: { 38 | 'site/index.html': ['README.md'] 39 | } 40 | } 41 | }, 42 | 'gh-pages': { 43 | options: { 44 | // Options for all targets go here. 45 | }, 46 | push: { 47 | options: { 48 | base: 'site' 49 | }, 50 | // These files will get pushed to the `gh-pages` branch (the default). 51 | src: ['**/*'] 52 | } 53 | } 54 | }); 55 | 56 | grunt.loadNpmTasks('grunt-contrib-copy'); 57 | grunt.loadNpmTasks('grunt-contrib-uglify'); 58 | grunt.loadNpmTasks('grunt-contrib-jshint'); 59 | grunt.loadNpmTasks('grunt-livemd'); 60 | grunt.loadNpmTasks('grunt-gh-pages'); 61 | 62 | grunt.registerTask('build', ['copy:dist', 'jshint', 'uglify']); 63 | grunt.registerTask('site', ['build', 'copy:site', 'livemd:site']); 64 | grunt.registerTask('default', ['build']); 65 | 66 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jquery-countimator 2 | ================== 3 | 4 | > Animated counter 5 | 6 | [Demo](http://benignware.github.io/jquery-countimator) 7 | 8 | ## Usage 9 | 10 | Include dependencies. 11 | 12 | ```html 13 | 14 | 15 | ``` 16 | 17 | ```js 18 | $(function() { 19 | $(".counter").countimator(); 20 | }); 21 | ``` 22 | 23 | ```html 24 | You got to count it 1000 times 25 | ``` 26 | 27 | ### Using inline html 28 | ```html 29 | 30 | You achieved 120 31 | out of 1000 points 32 | 33 | ``` 34 | 35 | ### Using a template-engine 36 | Countimator supports templates with [Handlebars](http://handlebarsjs.com/) 37 | 38 | Include handlebars as dependency: 39 | 40 | ```html 41 | 42 | ``` 43 | 44 | You may apply a template in three different ways: 45 | 46 | * Using the template-option 47 | * Using an inline template 48 | * Using a selector 49 | 50 | #### Using the template-option 51 | ```html+handlebars 52 |
56 |
57 | ``` 58 | 59 | #### Using an inline template 60 | ```html+handlebars 61 |
64 | 67 |
68 | ``` 69 | 70 | #### Using a selector 71 | ```html+handlebars 72 |
76 |
77 | 80 | ``` 81 | 82 | Number formatting 83 | ----------------- 84 | Use the following options to format values used by countimator: `decimals`, `decimalDelimiter`,`thousandDelimiter` 85 | ```html 86 | 0 EUR 92 | 93 | ``` 94 | Pad leading zeros by using the `pad`-option 95 | 96 | ```html 97 | 000 % 101 | 102 | ``` 103 | 104 | ## Trigger update 105 | 106 | To trigger the animation from an event at runtime, just call countimator again with a new value: 107 | 108 | ```html 109 | 0 EUR 115 | 116 | 119 | ``` 120 | 121 | ```js 122 | $('#update-counter').on('click', function() { 123 | $(this).fadeOut(500).prev().countimator({ 124 | value: 22000.12 125 | }); 126 | }); 127 | ``` 128 | 129 | ## Callbacks 130 | 131 | Get notified when animation changes by providing a callback function to `start`, `step` or `complete`-option. 132 | 133 | ```html 134 |
139 | You achieved 0 out of 1000 points. 140 |
141 | ``` 142 | 143 | ```css 144 | .counter-callbacks { 145 | transition: all 0.5s ease-out; 146 | position: relative; 147 | top: 0; 148 | opacity: 1; 149 | } 150 | .counter-callbacks:after { 151 | transition: all 0.5s ease-out; 152 | -webkit-transition: all 0.5s ease-out; 153 | opacity: 0; 154 | content: "New Highscore!"; 155 | font-size: 60%; 156 | vertical-align: top; 157 | background: #ddd; 158 | border-radius: 4px; 159 | padding: 4px; 160 | } 161 | .counter-callbacks.highscore:after { 162 | opacity: 1; 163 | } 164 | .counter-callbacks.highscore { 165 | color: teal; 166 | } 167 | .counter-callbacks.running, 168 | .counter-callbacks.complete { 169 | font-size: 22px; 170 | } 171 | .counter-callbacks.complete { 172 | top: -1em; 173 | opacity: 0; 174 | transition-duration: 2s; 175 | transition-delay: 1s; 176 | } 177 | ``` 178 | 179 | ```js 180 | $('.counter-callbacks').countimator({ 181 | start: function(count, options) { 182 | $(this).toggleClass('running'); 183 | }, 184 | step: function(count, options) { 185 | $(this).toggleClass('highscore', count > $(this).data('highscore')); 186 | }, 187 | complete: function() { 188 | $(this).toggleClass('running'); 189 | $(this).toggleClass('complete'); 190 | } 191 | }); 192 | ``` 193 | 194 | 195 | ## Wheel 196 | 197 | Countimator is shipped with a custom wheel-style. 198 | 199 | Add the wheel-plugin after jquery.countimator.js 200 | 201 | ```html 202 | 203 | ``` 204 | 205 | Include the wheel stylesheet. 206 | 207 | ```html 208 | 209 | ``` 210 | 211 | ```html 212 |
0 218 |
219 | ``` 220 | 221 | ```css 222 | .counter-wheel { 223 | color: teal; 224 | } 225 | ``` 226 | 227 | ### Customize 228 | 229 | See the following code for an example of using the wheel-plugin with styles, callbacks and triggers: 230 | 231 | ```html 232 |
237 |
238 | Your
239 |
00/12
240 | Score 241 |
242 |
243 | 244 | ``` 245 | 246 | Customize appearance using css: 247 | 248 | ```css 249 | .counter-wheel-callbacks { 250 | width: 200px; 251 | height: 200px; 252 | border-color: #ddd; 253 | border-width: 10px; 254 | background: #101433; 255 | text-transform: uppercase; 256 | font-family: inherit; 257 | font-size: 16px; 258 | padding: 15px; 259 | line-height: 28px; 260 | } 261 | 262 | .counter-wheel-callbacks .counter-wheel-content { 263 | background: #fff; 264 | color: #000; 265 | } 266 | 267 | .counter-wheel-callbacks .counter-wheel-content > div { 268 | font-weight: bold; 269 | font-size: 32px; 270 | } 271 | 272 | .counter-wheel-callbacks .counter-wheel-content > div > * { 273 | margin: 0 5px; 274 | } 275 | 276 | .counter-wheel-callbacks .counter-wheel-highlight { 277 | transition: all .25s ease-in; 278 | -webkit-transition: all .25s ease-in; 279 | color: #E71232; 280 | } 281 | 282 | .counter-level-warn .counter-wheel-highlight { 283 | color: orange; 284 | } 285 | 286 | .counter-level-ok .counter-wheel-highlight { 287 | color: green; 288 | } 289 | ``` 290 | 291 | Initialize countimator with callbacks and register button listener 292 | ```js 293 | $(function() { 294 | $('.counter-wheel-callbacks').countimator({ 295 | step: function(count, options) { 296 | var 297 | p = count / options.max; 298 | $(this).toggleClass('counter-level-ok', p >= 0.5); 299 | $(this).toggleClass('counter-level-warn', p >= 0.25 && p < 0.5); 300 | $(this).toggleClass('counter-level-critical', p < 0.25); 301 | } 302 | }); 303 | $('.counter-wheel-callbacks + button').on('click', function() { 304 | var countimator = $('.counter-wheel-callbacks').data('countimator'); 305 | $(this).fadeOut(500).prev().countimator({ 306 | value: 8 307 | }); 308 | }); 309 | }); 310 | ``` 311 | 312 | ## Options 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 |
NameDescription
animateOnAppearSpecifies whether to start animation when scrolled into view. Defaults to `true`
animateOnInitSpecifies whether to start animation when initialized. Defaults to `true`
completeCallback function to be executed when animation completes.
countCurrent animation count. Updated on step. Defaults to `0`
countSelectorSpecifies the selector of count element. Defaults to `'.counter-count'`
decimalsSpecifies the number of decimals for number formatting. Defaults to `0`
decimalDelimiterSpecifies a decimal separator for number formatting. Defaults to `.`
durationSpecifies the animation duration in milliseconds. Defaults to `1400`
engineSpecifies the template engine to use. `Handlebars` used, if defined
maxSpecifies the maximum value of the animation. Defaults to `0`
maxSelectorSpecifies the selector of maximum element. Defaults to `'.counter-max'`
minSpecifies the minimum value of the animation. Defaults to `null`
padSpecifies the number of digits to be padded with leading zeros
startCallback function to be executed when animation starts.
stepCallback function to be executed when animation on animation step.
styleSpecifies a custom style. Either provide a string identifier of a predefined style or an object containing a `render`-method.
templateEither specifies an inline-template or a selector for dom-template.
thousandDelimiterSpecifies a thousand delimiter for number formatting. Defaults to `null`
valueSpecifies the target value of the animation. Defaults to `null`
-------------------------------------------------------------------------------- /dist/css/jquery.countimator.wheel.css: -------------------------------------------------------------------------------- 1 | .counter-wheel { 2 | width: 5em; 3 | height: 5em; 4 | position: relative; 5 | border: 1px solid #ddd; 6 | border-radius: 50%; 7 | position: relative; 8 | box-sizing: border-box; 9 | padding: 0.5em; 10 | font-family: monospace; 11 | background: #fff; 12 | color: #000; 13 | display: inline-block; 14 | vertical-align: middle; 15 | } 16 | 17 | .counter-wheel:before { 18 | left: 0; 19 | right: 0; 20 | bottom: 0; 21 | position: absolute; 22 | max-width:100%; 23 | width: 100%; 24 | height: 100%; 25 | background: inherit; 26 | content: ""; 27 | display: block; 28 | border-radius: 50%; 29 | } 30 | 31 | .counter-wheel-content { 32 | content: ""; 33 | text-align: center; 34 | border-width: inherit; 35 | border-style: solid; 36 | border-color: inherit; 37 | border-radius: 50%; 38 | background: inherit; 39 | box-sizing: border-box; 40 | width: 100%; 41 | height: 100%; 42 | display: block; 43 | position: relative; 44 | 45 | display: -webkit-box; 46 | -webkit-box-orient: vertical; 47 | -webkit-box-pack: center; 48 | -webkit-box-align: center; 49 | 50 | display: -moz-box; 51 | -moz-box-orient: vertical; 52 | -moz-box-pack: center; 53 | -moz-box-align: center; 54 | 55 | display: box; 56 | box-orient: vertical; 57 | box-pack: center; 58 | box-align: center; 59 | 60 | max-height: 100%; 61 | } 62 | 63 | .counter-wheel svg { 64 | top: 0; 65 | left: 0; 66 | right: 0; 67 | bottom: 0; 68 | position: absolute; 69 | max-width:100%; 70 | width: 100%; 71 | height: 100%; 72 | } 73 | 74 | .counter-wheel svg .counter-wheel-highlight { 75 | fill: currentColor; 76 | } 77 | 78 | .counter-wheel-highlight { 79 | color: teal; 80 | color: currentColor; 81 | } 82 | -------------------------------------------------------------------------------- /dist/js/jquery.countimator.js: -------------------------------------------------------------------------------- 1 | (function ( $, window ) { 2 | 3 | var 4 | pluginName = 'countimator', 5 | defaults = { 6 | // Values 7 | count: 0, 8 | value: null, 9 | min: null, 10 | max: 0, 11 | // Animation options 12 | duration: 1000, 13 | // Property selector 14 | countSelector: '.counter-count', 15 | maxSelector: '.counter-max', 16 | // Template options 17 | template: null, 18 | engine: null, 19 | // Trigger animation options 20 | animateOnInit: true, 21 | animateOnAppear: true, 22 | // Format options 23 | decimals: 0, 24 | decimalDelimiter: '.', 25 | thousandDelimiter: null, 26 | pad: false, 27 | // Style plugin 28 | style: null, 29 | // Callbacks 30 | start: function() {}, 31 | step: function(step) {}, 32 | complete: function() {} 33 | }, 34 | 35 | /** 36 | * Format a number 37 | * @param {Object} n 38 | * @param {Object} decimals 39 | * @param {Object} decimalDelimiter 40 | * @param {Object} thousandDelimiter 41 | */ 42 | formatNumber = function(number, decimals, decimalDelimiter, thousandDelimiter) { 43 | decimals = isNaN(decimals = Math.abs(decimals)) ? 2 : decimals; 44 | decimalDelimiter = typeof decimalDelimiter === 'undefined' ? "." : decimalDelimiter; 45 | thousandDelimiter = typeof thousandDelimiter === 'undefined' ? "," : thousandDelimiter; 46 | thousandDelimiter = typeof thousandDelimiter === 'string' ? thousandDelimiter : ""; 47 | var 48 | s = number < 0 ? "-" : "", 49 | n = Math.abs(+number || 0).toFixed(decimals), 50 | i = String(parseInt(n)), 51 | j = (i.length > 3 ? i.length % 3 : 0); 52 | 53 | return s + (j ? i.substr(0, j) + thousandDelimiter : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + thousandDelimiter) + (decimals ? decimalDelimiter + Math.abs(n - i).toFixed(decimals).slice(2) : ""); 54 | }, 55 | 56 | /** 57 | * Pad a number with leading zeros 58 | * @param {Object} number 59 | * @param {Object} length 60 | */ 61 | pad = function(number, length) { 62 | var str = '' + number; 63 | while (str.length < length) { 64 | str = '0' + str; 65 | } 66 | return str; 67 | }, 68 | 69 | /** 70 | * Return parent's textnodes 71 | * @param {Object} parent 72 | */ 73 | textNodes = function(parent) { 74 | return $(parent).contents().filter(function () { 75 | return this.nodeType === 3; 76 | }); 77 | }, 78 | 79 | /** 80 | * Detect if element is in viewport 81 | * @param {Object} elem 82 | */ 83 | inView = function(elem){ 84 | var 85 | $elem = $(elem), 86 | $window = $(window), 87 | docViewTop = $window.scrollTop(), 88 | docViewBottom = docViewTop + $window.height(), 89 | elemTop = $elem.offset().top, 90 | elemBottom = elemTop + $elem.height(); 91 | return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop)); 92 | }, 93 | 94 | /** 95 | * A Polyfill for requestAnimationFrame 96 | */ 97 | requestAnimationFrame = 98 | window.mozRequestAnimationFrame || 99 | window.webkitRequestAnimationFrame || 100 | window.msRequestAnimationFrame || 101 | window.oRequestAnimationFrame || 102 | function( callback ){ 103 | window.setTimeout(callback, 1000 / 60); 104 | }; 105 | 106 | 107 | /** 108 | * Countimator 109 | * @param {Object} element 110 | * @param {Object} options 111 | */ 112 | function Countimator(element, options) { 113 | 114 | var 115 | instance = this, 116 | $element = $(element), 117 | animating = false, 118 | startTime, startCount; 119 | 120 | options = $.extend({}, defaults, options, $element.data()); 121 | 122 | // Private Methods 123 | function init() { 124 | 125 | var 126 | value = getValue(), 127 | count = getCount(), 128 | countNode, 129 | max = getMax(), 130 | maxNode, 131 | script; 132 | 133 | // Init values 134 | if (!count) { 135 | countNode = getCountNode(); 136 | if (countNode) { 137 | if (typeof options.value !== 'number') { 138 | options.value = countNode.nodeValue; 139 | } else { 140 | options.count = countNode.nodeValue; 141 | } 142 | } 143 | } 144 | 145 | if (!max) { 146 | maxNode = getMaxNode(); 147 | if (maxNode) { 148 | options.max = maxNode.nodeValue; 149 | } 150 | } 151 | 152 | // Init template 153 | script = $element.find("script[type*='text/x-']"); 154 | if (script.length) { 155 | options.template = script.html(); 156 | script.remove(); 157 | } 158 | 159 | // Init listeners 160 | $(window).on('resize', function() { 161 | resize.call(instance); 162 | }); 163 | 164 | function scrollListener() { 165 | if (options.animateOnInit && options.animateOnAppear && inView(element)) { 166 | $(window).off('scroll touchmove', scrollListener); 167 | start.call(instance); 168 | } 169 | } 170 | 171 | $(window).on('scroll touchmove', scrollListener); 172 | 173 | if (options.animateOnInit) { 174 | if (options.animateOnAppear && inView(element)) { 175 | options.count = typeof count === 'number' ? count : 0; 176 | start.call(instance); 177 | } else { 178 | render.call(this); 179 | } 180 | } else { 181 | options.count = getValue(); 182 | render.call(this); 183 | } 184 | 185 | resize.call(this); 186 | } 187 | 188 | function setOption(name, value) { 189 | var 190 | old = options[name]; 191 | options[name] = value; 192 | switch (name) { 193 | case 'value': 194 | if (old === value) { 195 | return; 196 | } 197 | if (typeof old !== 'number') { 198 | options['count'] = value; 199 | render.call(this); 200 | } else { 201 | options['count'] = old; 202 | start(); 203 | } 204 | break; 205 | } 206 | } 207 | 208 | function getMin() { 209 | var 210 | min = parseFloat(options.min); 211 | return isNaN(min) ? 0 : min; 212 | } 213 | 214 | function getMax() { 215 | var 216 | max = parseFloat(options.max); 217 | return isNaN(max) ? 0 : max; 218 | } 219 | 220 | function getValue() { 221 | var 222 | max = getMax(), 223 | min = getMin(), 224 | count = getCount(), 225 | value = parseFloat(options.value); 226 | if (isNaN(value)) { 227 | value = min; 228 | } 229 | return value; 230 | } 231 | 232 | function getCount() { 233 | var 234 | max = getMax(), 235 | min = getMin(), 236 | count = parseFloat(options.count); 237 | if (isNaN(count)) { 238 | count = min; 239 | } 240 | return count; 241 | } 242 | 243 | function resize() { 244 | } 245 | 246 | function getCountNode(count) { 247 | var 248 | countElement = $element.find(options.countSelector)[0]; 249 | if (!countElement) { 250 | countElement = $element.find("*").last().siblings().addBack()[0]; 251 | } 252 | return textNodes(countElement || element)[0]; 253 | } 254 | 255 | function getMaxNode(count) { 256 | var 257 | maxElement = $element.find(options.maxSelector)[0]; 258 | if (maxElement) { 259 | return textNodes(maxElement)[0]; 260 | } 261 | return null; 262 | } 263 | 264 | function getFormattedValue(value) { 265 | // format number 266 | var 267 | decimals = options.decimals, 268 | decimalDelimiter = options.decimalDelimiter, 269 | thousandDelimiter = options.thousandDelimiter, 270 | string = formatNumber(value, decimals, decimalDelimiter, thousandDelimiter); 271 | // Pad 272 | string = pad(string, options.pad); 273 | return string; 274 | } 275 | 276 | function render() { 277 | 278 | var 279 | max = getMax(), 280 | min = getMin(), 281 | value = getValue(), 282 | count = getCount(), 283 | formattedCount = getFormattedValue(count), 284 | formattedValue = getFormattedValue(value), 285 | formattedMax = getFormattedValue(max), 286 | formattedMin = getFormattedValue(min), 287 | engine = options.engine || typeof window['Handlebars'] !== 'undefined' ? window['Handlebars'] : null, 288 | template = options.template, 289 | string, div, $template, tmpl, tmplData, nodeList, countNode, maxNode, style; 290 | 291 | try { 292 | $template = $(options.template); 293 | template = $template.length && $template[0].innerHTML || template; 294 | } catch (e) { 295 | // Template is not a dom element 296 | } 297 | 298 | if (engine && template) { 299 | // Template engine 300 | tmpl = engine.compile(template); 301 | if (tmpl) { 302 | tmplData = $.extend({}, options, {count: formattedCount, value: formattedValue, max: formattedMax, min: formattedMin}); 303 | string = tmpl(tmplData); 304 | } 305 | div = document.createElement('div'); 306 | div.innerHTML = string; 307 | nodeList = div.childNodes; 308 | $(element).contents().remove(); 309 | $(element).append(nodeList); 310 | 311 | } else { 312 | // Classic approach without a template engine 313 | countNode = getCountNode(); 314 | if (countNode) { 315 | countNode.nodeValue = formattedCount; 316 | } 317 | maxNode = getMaxNode(); 318 | if (maxNode) { 319 | maxNode.nodeValue = formattedMax; 320 | } 321 | if (!countNode && !maxNode) { 322 | element.innerHTML = formattedCount; 323 | } 324 | } 325 | 326 | if (options.style) { 327 | style = $.fn[pluginName].getStyle(options.style); 328 | if (style && style.render) { 329 | style.render.call(element, count, options); 330 | } 331 | } 332 | 333 | } 334 | 335 | function animate(value) { 336 | options.value = value; 337 | if (!animating) { 338 | start(); 339 | } 340 | } 341 | 342 | function start() { 343 | if (!animating) { 344 | startTime = new Date().getTime(); 345 | startCount = getCount(); 346 | animating = true; 347 | if (typeof options.start === 'function') { 348 | options.start.call(element); 349 | } 350 | requestAnimationFrame(step); 351 | } 352 | } 353 | 354 | function step() { 355 | 356 | var 357 | duration = options.duration, 358 | max = getMax(), 359 | value = getValue(), 360 | currentTime = new Date().getTime(), 361 | endTime = startTime + duration, 362 | currentStep = Math.min((duration - (endTime - currentTime)) / duration, 1), 363 | count = startCount + currentStep * (value - startCount); 364 | 365 | options.count = count; 366 | 367 | render.call(this); 368 | 369 | // Step Callback 370 | if (typeof options.step === 'function') { 371 | options.step.call(element, count, options); 372 | } 373 | 374 | if (currentStep < 1 && animating) { 375 | // Run loop 376 | requestAnimationFrame(step); 377 | } else { 378 | // Complete 379 | stop.call(this); 380 | } 381 | } 382 | 383 | 384 | function stop() { 385 | animating = false; 386 | if (typeof options.complete === 'function') { 387 | options.complete.call(element); 388 | } 389 | } 390 | 391 | // Public methods 392 | 393 | this.resize = function() { 394 | resize.call(this); 395 | }; 396 | 397 | this.animate = function(value) { 398 | animate.call(this, value); 399 | }; 400 | 401 | this.setOptions = function(opts) { 402 | var old = this.getOptions(); 403 | $.extend(true, options, opts); 404 | if (options.value !== old.value) { 405 | start(); 406 | } 407 | }; 408 | 409 | this.getOptions = function() { 410 | return $.extend(true, {}, options); 411 | }; 412 | 413 | // Init 414 | init.call(this); 415 | 416 | } 417 | 418 | 419 | // Bootstrap JQuery-Plugin 420 | $.fn[pluginName] = function(options) { 421 | return this.each(function() { 422 | var 423 | opts = $.extend(true, {}, options), 424 | countimator = $(this).data(pluginName); 425 | if (!countimator) { 426 | $(this).data(pluginName, new Countimator(this, opts)); 427 | } else { 428 | countimator.setOptions(opts); 429 | } 430 | return $(this); 431 | }); 432 | }; 433 | 434 | 435 | // Style api 436 | (function() { 437 | var 438 | styles = {}; 439 | $.fn[pluginName].registerStyle = function(name, def) { 440 | styles[name] = def; 441 | }; 442 | 443 | $.fn[pluginName].getStyle = function(name) { 444 | return styles[name]; 445 | }; 446 | 447 | })(); 448 | 449 | 450 | })( jQuery, window ); -------------------------------------------------------------------------------- /dist/js/jquery.countimator.min.js: -------------------------------------------------------------------------------- 1 | /*! jquery-countimator 14-10-2015 */ 2 | !function(a,b){function c(c,k){function l(){function d(){k.animateOnInit&&k.animateOnAppear&&i(c)&&(a(b).off("scroll touchmove",d),w.call(B))}var e,f,g,h=(o(),p()),j=n();h||(e=r(),e&&("number"!=typeof k.value?k.value=e.nodeValue:k.count=e.nodeValue)),j||(f=s(),f&&(k.max=f.nodeValue)),g=C.find("script[type*='text/x-']"),g.length&&(k.template=g.html(),g.remove()),a(b).on("resize",function(){q.call(B)}),a(b).on("scroll touchmove",d),k.animateOnInit?k.animateOnAppear&&i(c)?(k.count="number"==typeof h?h:0,w.call(B)):u.call(this):(k.count=o(),u.call(this)),q.call(this)}function m(){var a=parseFloat(k.min);return isNaN(a)?0:a}function n(){var a=parseFloat(k.max);return isNaN(a)?0:a}function o(){var a=(n(),m()),b=(p(),parseFloat(k.value));return isNaN(b)&&(b=a),b}function p(){var a=(n(),m()),b=parseFloat(k.count);return isNaN(b)&&(b=a),b}function q(){}function r(a){var b=C.find(k.countSelector)[0];return b||(b=C.find("*").last().siblings().addBack()[0]),h(b||c)[0]}function s(a){var b=C.find(k.maxSelector)[0];return b?h(b)[0]:null}function t(a){var b=k.decimals,c=k.decimalDelimiter,d=k.thousandDelimiter,e=f(a,b,c,d);return e=g(e,k.pad)}function u(){var e,f,g,h,i,j,l,q,u,v=n(),w=m(),x=o(),y=p(),z=t(y),A=t(x),B=t(v),C=t(w),D=k.engine||"undefined"!=typeof b.Handlebars?b.Handlebars:null,E=k.template;try{g=a(k.template),E=g.length&&g[0].innerHTML||E}catch(F){}D&&E?(h=D.compile(E),h&&(i=a.extend({},k,{count:z,value:A,max:B,min:C}),e=h(i)),f=document.createElement("div"),f.innerHTML=e,j=f.childNodes,a(c).contents().remove(),a(c).append(j)):(l=r(),l&&(l.nodeValue=z),q=s(),q&&(q.nodeValue=B),l||q||(c.innerHTML=z)),k.style&&(u=a.fn[d].getStyle(k.style),u&&u.render&&u.render.call(c,y,k))}function v(a){k.value=a,D||w()}function w(){D||(z=(new Date).getTime(),A=p(),D=!0,"function"==typeof k.start&&k.start.call(c),j(x))}function x(){var a=k.duration,b=(n(),o()),d=(new Date).getTime(),e=z+a,f=Math.min((a-(e-d))/a,1),g=A+f*(b-A);k.count=g,u.call(this),"function"==typeof k.step&&k.step.call(c,g,k),1>f&&D?j(x):y.call(this)}function y(){D=!1,"function"==typeof k.complete&&k.complete.call(c)}var z,A,B=this,C=a(c),D=!1;k=a.extend({},e,k,C.data()),this.resize=function(){q.call(this)},this.animate=function(a){v.call(this,a)},this.setOptions=function(b){var c=this.getOptions();a.extend(!0,k,b),k.value!==c.value&&w()},this.getOptions=function(){return a.extend(!0,{},k)},l.call(this)}var d="countimator",e={count:0,value:null,min:null,max:0,duration:1e3,countSelector:".counter-count",maxSelector:".counter-max",template:null,engine:null,animateOnInit:!0,animateOnAppear:!0,decimals:0,decimalDelimiter:".",thousandDelimiter:null,pad:!1,style:null,start:function(){},step:function(a){},complete:function(){}},f=function(a,b,c,d){b=isNaN(b=Math.abs(b))?2:b,c="undefined"==typeof c?".":c,d="undefined"==typeof d?",":d,d="string"==typeof d?d:"";var e=0>a?"-":"",f=Math.abs(+a||0).toFixed(b),g=String(parseInt(f)),h=g.length>3?g.length%3:0;return e+(h?g.substr(0,h)+d:"")+g.substr(h).replace(/(\d{3})(?=\d)/g,"$1"+d)+(b?c+Math.abs(f-g).toFixed(b).slice(2):"")},g=function(a,b){for(var c=""+a;c.length=i&&h>=f},j=b.mozRequestAnimationFrame||b.webkitRequestAnimationFrame||b.msRequestAnimationFrame||b.oRequestAnimationFrame||function(a){b.setTimeout(a,1e3/60)};a.fn[d]=function(b){return this.each(function(){var e=a.extend(!0,{},b),f=a(this).data(d);return f?f.setOptions(e):a(this).data(d,new c(this,e)),a(this)})},function(){var b={};a.fn[d].registerStyle=function(a,c){b[a]=c},a.fn[d].getStyle=function(a){return b[a]}}()}(jQuery,window); -------------------------------------------------------------------------------- /dist/js/jquery.countimator.wheel.js: -------------------------------------------------------------------------------- 1 | (function ( $, window ) { 2 | 3 | function arcPath(cx, cy, r, sAngle, eAngle, counterclockwise) { 4 | counterclockwise = typeof counterclockwise === 'boolean' ? counterclockwise : false; 5 | sAngle-= Math.PI / 2; 6 | eAngle-= Math.PI / 2; 7 | var 8 | d = 'M ' + cx + ', ' + cy, 9 | cxs, 10 | cys, 11 | cxe, 12 | cye; 13 | if (eAngle - sAngle === Math.PI * 2) { 14 | // Circle 15 | d+= ' m -' + r + ', 0 a ' + r + ',' + r + ' 0 1,0 ' + (r * 2) + ',0 a ' + r + ',' + r + ' 0 1,0 -' + (r * 2) + ',0'; 16 | } else { 17 | cxs = cx + Math.cos(sAngle) * r; 18 | cys = cy + Math.sin(sAngle) * r; 19 | cxe = cx + Math.cos(eAngle) * r; 20 | cye = cy + Math.sin(eAngle) * r; 21 | d+= " L" + cxs + "," + cys + 22 | " A" + r + "," + r + " 0 " + (eAngle - sAngle > Math.PI ? 1 : 0) + "," + (counterclockwise ? 0 : 1) + 23 | " " + cxe + "," + cye + " Z"; 24 | } 25 | return d; 26 | } 27 | 28 | var wheelStyle = { 29 | render: function(count, options) { 30 | var 31 | radius = 200, 32 | min = options.min ? parseFloat(options.min) : 0, 33 | max = options.max ? parseFloat(options.max) : 0, 34 | cx = radius, 35 | cy = radius, 36 | r = radius, 37 | p = (count - min) / (max - min), 38 | a = p * Math.PI * 2, 39 | d = arcPath(cx, cy, r + 1, 0, a), 40 | $graphics = $(this).find('> .counter-wheel-graphics'), 41 | 42 | $content = $(this).find('> .counter-wheel-content'); 43 | 44 | count = Math.min(max, Math.max(min, count)); 45 | 46 | if (!$content.length) { 47 | $(this).prepend($(this).wrapInner('
')); 48 | } 49 | 50 | if (!$graphics.length) { 51 | $graphics = $( 52 | '' + 53 | '' + 54 | '' 55 | ); 56 | $(this).prepend($graphics); 57 | } 58 | 59 | $graphics.find('path').attr('d', d); 60 | 61 | } 62 | }; 63 | 64 | if (!$.fn.countimator) { 65 | throw 'Include countimator script before style-plugin'; 66 | } 67 | 68 | // Add custom style to plugin registry 69 | $.fn.countimator.registerStyle('wheel', wheelStyle); 70 | 71 | })(jQuery, window); -------------------------------------------------------------------------------- /dist/js/jquery.countimator.wheel.min.js: -------------------------------------------------------------------------------- 1 | /*! jquery-countimator 14-10-2015 */ 2 | !function(a,b){function c(a,b,c,d,e,f){f="boolean"==typeof f?f:!1,d-=Math.PI/2,e-=Math.PI/2;var g,h,i,j,k="M "+a+", "+b;return e-d===2*Math.PI?k+=" m -"+c+", 0 a "+c+","+c+" 0 1,0 "+2*c+",0 a "+c+","+c+" 0 1,0 -"+2*c+",0":(g=a+Math.cos(d)*c,h=b+Math.sin(d)*c,i=a+Math.cos(e)*c,j=b+Math.sin(e)*c,k+=" L"+g+","+h+" A"+c+","+c+" 0 "+(e-d>Math.PI?1:0)+","+(f?0:1)+" "+i+","+j+" Z"),k}var d={render:function(b,d){var e=200,f=d.min?parseFloat(d.min):0,g=d.max?parseFloat(d.max):0,h=e,i=e,j=e,k=(b-f)/(g-f),l=k*Math.PI*2,m=c(h,i,j+1,0,l),n=a(this).find("> .counter-wheel-graphics"),o=a(this).find("> .counter-wheel-content");b=Math.min(g,Math.max(f,b)),o.length||a(this).prepend(a(this).wrapInner('
')),n.length||(n=a(''),a(this).prepend(n)),n.find("path").attr("d",m)}};if(!a.fn.countimator)throw"Include countimator script before style-plugin";a.fn.countimator.registerStyle("wheel",d)}(jQuery,window); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-countimator", 3 | "version": "0.0.2", 4 | "description": "Animated counter", 5 | "homepage": "http://benignware.github.io/jquery-countimator", 6 | "author": { 7 | "name": "Benignware", 8 | "email": "mail@benignware.com", 9 | "url": "https://github.com/benignware" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/benignware/jquery-countimator.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/benignware/jquery-countimator/issues" 17 | }, 18 | "devDependencies": { 19 | "grunt": "~0.4.5", 20 | "grunt-contrib-copy": "", 21 | "grunt-contrib-jshint": "^0.11.3", 22 | "grunt-contrib-uglify": "", 23 | "grunt-gh-pages": "^0.10.0", 24 | "grunt-livemd": "^0.0.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/css/jquery.countimator.wheel.css: -------------------------------------------------------------------------------- 1 | .counter-wheel { 2 | width: 5em; 3 | height: 5em; 4 | position: relative; 5 | border: 1px solid #ddd; 6 | border-radius: 50%; 7 | position: relative; 8 | box-sizing: border-box; 9 | padding: 0.5em; 10 | font-family: monospace; 11 | background: #fff; 12 | color: #000; 13 | display: inline-block; 14 | vertical-align: middle; 15 | } 16 | 17 | .counter-wheel:before { 18 | left: 0; 19 | right: 0; 20 | bottom: 0; 21 | position: absolute; 22 | max-width:100%; 23 | width: 100%; 24 | height: 100%; 25 | background: inherit; 26 | content: ""; 27 | display: block; 28 | border-radius: 50%; 29 | } 30 | 31 | .counter-wheel-content { 32 | content: ""; 33 | text-align: center; 34 | border-width: inherit; 35 | border-style: solid; 36 | border-color: inherit; 37 | border-radius: 50%; 38 | background: inherit; 39 | box-sizing: border-box; 40 | width: 100%; 41 | height: 100%; 42 | display: block; 43 | position: relative; 44 | 45 | display: -webkit-box; 46 | -webkit-box-orient: vertical; 47 | -webkit-box-pack: center; 48 | -webkit-box-align: center; 49 | 50 | display: -moz-box; 51 | -moz-box-orient: vertical; 52 | -moz-box-pack: center; 53 | -moz-box-align: center; 54 | 55 | display: box; 56 | box-orient: vertical; 57 | box-pack: center; 58 | box-align: center; 59 | 60 | max-height: 100%; 61 | } 62 | 63 | .counter-wheel svg { 64 | top: 0; 65 | left: 0; 66 | right: 0; 67 | bottom: 0; 68 | position: absolute; 69 | max-width:100%; 70 | width: 100%; 71 | height: 100%; 72 | } 73 | 74 | .counter-wheel svg .counter-wheel-highlight { 75 | fill: currentColor; 76 | } 77 | 78 | .counter-wheel-highlight { 79 | color: teal; 80 | color: currentColor; 81 | } 82 | -------------------------------------------------------------------------------- /src/js/jquery.countimator.js: -------------------------------------------------------------------------------- 1 | (function ( $, window ) { 2 | 3 | var 4 | pluginName = 'countimator', 5 | defaults = { 6 | // Values 7 | count: 0, 8 | value: null, 9 | min: null, 10 | max: 0, 11 | // Animation options 12 | duration: 1000, 13 | // Property selector 14 | countSelector: '.counter-count', 15 | maxSelector: '.counter-max', 16 | // Template options 17 | template: null, 18 | engine: null, 19 | // Trigger animation options 20 | animateOnInit: true, 21 | animateOnAppear: true, 22 | // Format options 23 | decimals: 0, 24 | decimalDelimiter: '.', 25 | thousandDelimiter: null, 26 | pad: false, 27 | // Style plugin 28 | style: null, 29 | // Callbacks 30 | start: function() {}, 31 | step: function(step) {}, 32 | complete: function() {} 33 | }, 34 | 35 | /** 36 | * Format a number 37 | * @param {Object} n 38 | * @param {Object} decimals 39 | * @param {Object} decimalDelimiter 40 | * @param {Object} thousandDelimiter 41 | */ 42 | formatNumber = function(number, decimals, decimalDelimiter, thousandDelimiter) { 43 | decimals = isNaN(decimals = Math.abs(decimals)) ? 2 : decimals; 44 | decimalDelimiter = typeof decimalDelimiter === 'undefined' ? "." : decimalDelimiter; 45 | thousandDelimiter = typeof thousandDelimiter === 'undefined' ? "," : thousandDelimiter; 46 | thousandDelimiter = typeof thousandDelimiter === 'string' ? thousandDelimiter : ""; 47 | var 48 | s = number < 0 ? "-" : "", 49 | n = Math.abs(+number || 0).toFixed(decimals), 50 | i = String(parseInt(n)), 51 | j = (i.length > 3 ? i.length % 3 : 0); 52 | 53 | return s + (j ? i.substr(0, j) + thousandDelimiter : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + thousandDelimiter) + (decimals ? decimalDelimiter + Math.abs(n - i).toFixed(decimals).slice(2) : ""); 54 | }, 55 | 56 | /** 57 | * Pad a number with leading zeros 58 | * @param {Object} number 59 | * @param {Object} length 60 | */ 61 | pad = function(number, length) { 62 | var str = '' + number; 63 | while (str.length < length) { 64 | str = '0' + str; 65 | } 66 | return str; 67 | }, 68 | 69 | /** 70 | * Return parent's textnodes 71 | * @param {Object} parent 72 | */ 73 | textNodes = function(parent) { 74 | return $(parent).contents().filter(function () { 75 | return this.nodeType === 3; 76 | }); 77 | }, 78 | 79 | /** 80 | * Detect if element is in viewport 81 | * @param {Object} elem 82 | */ 83 | inView = function(elem){ 84 | var 85 | $elem = $(elem), 86 | $window = $(window), 87 | docViewTop = $window.scrollTop(), 88 | docViewBottom = docViewTop + $window.height(), 89 | elemTop = $elem.offset().top, 90 | elemBottom = elemTop + $elem.height(); 91 | return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop)); 92 | }, 93 | 94 | /** 95 | * A Polyfill for requestAnimationFrame 96 | */ 97 | requestAnimationFrame = 98 | window.mozRequestAnimationFrame || 99 | window.webkitRequestAnimationFrame || 100 | window.msRequestAnimationFrame || 101 | window.oRequestAnimationFrame || 102 | function( callback ){ 103 | window.setTimeout(callback, 1000 / 60); 104 | }; 105 | 106 | 107 | /** 108 | * Countimator 109 | * @param {Object} element 110 | * @param {Object} options 111 | */ 112 | function Countimator(element, options) { 113 | 114 | var 115 | instance = this, 116 | $element = $(element), 117 | animating = false, 118 | startTime, startCount; 119 | 120 | options = $.extend({}, defaults, options, $element.data()); 121 | 122 | // Private Methods 123 | function init() { 124 | 125 | var 126 | value = getValue(), 127 | count = getCount(), 128 | countNode, 129 | max = getMax(), 130 | maxNode, 131 | script; 132 | 133 | // Init values 134 | if (!count) { 135 | countNode = getCountNode(); 136 | if (countNode) { 137 | if (typeof options.value !== 'number') { 138 | options.value = countNode.nodeValue; 139 | } else { 140 | options.count = countNode.nodeValue; 141 | } 142 | } 143 | } 144 | 145 | if (!max) { 146 | maxNode = getMaxNode(); 147 | if (maxNode) { 148 | options.max = maxNode.nodeValue; 149 | } 150 | } 151 | 152 | // Init template 153 | script = $element.find("script[type*='text/x-']"); 154 | if (script.length) { 155 | options.template = script.html(); 156 | script.remove(); 157 | } 158 | 159 | // Init listeners 160 | $(window).on('resize', function() { 161 | resize.call(instance); 162 | }); 163 | 164 | function scrollListener() { 165 | if (options.animateOnInit && options.animateOnAppear && inView(element)) { 166 | $(window).off('scroll touchmove', scrollListener); 167 | start.call(instance); 168 | } 169 | } 170 | 171 | $(window).on('scroll touchmove', scrollListener); 172 | 173 | if (options.animateOnInit) { 174 | if (options.animateOnAppear && inView(element)) { 175 | options.count = typeof count === 'number' ? count : 0; 176 | start.call(instance); 177 | } else { 178 | render.call(this); 179 | } 180 | } else { 181 | options.count = getValue(); 182 | render.call(this); 183 | } 184 | 185 | resize.call(this); 186 | } 187 | 188 | function setOption(name, value) { 189 | var 190 | old = options[name]; 191 | options[name] = value; 192 | switch (name) { 193 | case 'value': 194 | if (old === value) { 195 | return; 196 | } 197 | if (typeof old !== 'number') { 198 | options['count'] = value; 199 | render.call(this); 200 | } else { 201 | options['count'] = old; 202 | start(); 203 | } 204 | break; 205 | } 206 | } 207 | 208 | function getMin() { 209 | var 210 | min = parseFloat(options.min); 211 | return isNaN(min) ? 0 : min; 212 | } 213 | 214 | function getMax() { 215 | var 216 | max = parseFloat(options.max); 217 | return isNaN(max) ? 0 : max; 218 | } 219 | 220 | function getValue() { 221 | var 222 | max = getMax(), 223 | min = getMin(), 224 | count = getCount(), 225 | value = parseFloat(options.value); 226 | if (isNaN(value)) { 227 | value = min; 228 | } 229 | return value; 230 | } 231 | 232 | function getCount() { 233 | var 234 | max = getMax(), 235 | min = getMin(), 236 | count = parseFloat(options.count); 237 | if (isNaN(count)) { 238 | count = min; 239 | } 240 | return count; 241 | } 242 | 243 | function resize() { 244 | } 245 | 246 | function getCountNode(count) { 247 | var 248 | countElement = $element.find(options.countSelector)[0]; 249 | if (!countElement) { 250 | countElement = $element.find("*").last().siblings().addBack()[0]; 251 | } 252 | return textNodes(countElement || element)[0]; 253 | } 254 | 255 | function getMaxNode(count) { 256 | var 257 | maxElement = $element.find(options.maxSelector)[0]; 258 | if (maxElement) { 259 | return textNodes(maxElement)[0]; 260 | } 261 | return null; 262 | } 263 | 264 | function getFormattedValue(value) { 265 | // format number 266 | var 267 | decimals = options.decimals, 268 | decimalDelimiter = options.decimalDelimiter, 269 | thousandDelimiter = options.thousandDelimiter, 270 | string = formatNumber(value, decimals, decimalDelimiter, thousandDelimiter); 271 | // Pad 272 | string = pad(string, options.pad); 273 | return string; 274 | } 275 | 276 | function render() { 277 | 278 | var 279 | max = getMax(), 280 | min = getMin(), 281 | value = getValue(), 282 | count = getCount(), 283 | formattedCount = getFormattedValue(count), 284 | formattedValue = getFormattedValue(value), 285 | formattedMax = getFormattedValue(max), 286 | formattedMin = getFormattedValue(min), 287 | engine = options.engine || typeof window['Handlebars'] !== 'undefined' ? window['Handlebars'] : null, 288 | template = options.template, 289 | string, div, $template, tmpl, tmplData, nodeList, countNode, maxNode, style; 290 | 291 | try { 292 | $template = $(options.template); 293 | template = $template.length && $template[0].innerHTML || template; 294 | } catch (e) { 295 | // Template is not a dom element 296 | } 297 | 298 | if (engine && template) { 299 | // Template engine 300 | tmpl = engine.compile(template); 301 | if (tmpl) { 302 | tmplData = $.extend({}, options, {count: formattedCount, value: formattedValue, max: formattedMax, min: formattedMin}); 303 | string = tmpl(tmplData); 304 | } 305 | div = document.createElement('div'); 306 | div.innerHTML = string; 307 | nodeList = div.childNodes; 308 | $(element).contents().remove(); 309 | $(element).append(nodeList); 310 | 311 | } else { 312 | // Classic approach without a template engine 313 | countNode = getCountNode(); 314 | if (countNode) { 315 | countNode.nodeValue = formattedCount; 316 | } 317 | maxNode = getMaxNode(); 318 | if (maxNode) { 319 | maxNode.nodeValue = formattedMax; 320 | } 321 | if (!countNode && !maxNode) { 322 | element.innerHTML = formattedCount; 323 | } 324 | } 325 | 326 | if (options.style) { 327 | style = $.fn[pluginName].getStyle(options.style); 328 | if (style && style.render) { 329 | style.render.call(element, count, options); 330 | } 331 | } 332 | 333 | } 334 | 335 | function animate(value) { 336 | options.value = value; 337 | if (!animating) { 338 | start(); 339 | } 340 | } 341 | 342 | function start() { 343 | if (!animating) { 344 | startTime = new Date().getTime(); 345 | startCount = getCount(); 346 | animating = true; 347 | if (typeof options.start === 'function') { 348 | options.start.call(element); 349 | } 350 | requestAnimationFrame(step); 351 | } 352 | } 353 | 354 | function step() { 355 | 356 | var 357 | duration = options.duration, 358 | max = getMax(), 359 | value = getValue(), 360 | currentTime = new Date().getTime(), 361 | endTime = startTime + duration, 362 | currentStep = Math.min((duration - (endTime - currentTime)) / duration, 1), 363 | count = startCount + currentStep * (value - startCount); 364 | 365 | options.count = count; 366 | 367 | render.call(this); 368 | 369 | // Step Callback 370 | if (typeof options.step === 'function') { 371 | options.step.call(element, count, options); 372 | } 373 | 374 | if (currentStep < 1 && animating) { 375 | // Run loop 376 | requestAnimationFrame(step); 377 | } else { 378 | // Complete 379 | stop.call(this); 380 | } 381 | } 382 | 383 | 384 | function stop() { 385 | animating = false; 386 | if (typeof options.complete === 'function') { 387 | options.complete.call(element); 388 | } 389 | } 390 | 391 | // Public methods 392 | 393 | this.resize = function() { 394 | resize.call(this); 395 | }; 396 | 397 | this.animate = function(value) { 398 | animate.call(this, value); 399 | }; 400 | 401 | this.setOptions = function(opts) { 402 | var old = this.getOptions(); 403 | $.extend(true, options, opts); 404 | if (options.value !== old.value) { 405 | start(); 406 | } 407 | }; 408 | 409 | this.getOptions = function() { 410 | return $.extend(true, {}, options); 411 | }; 412 | 413 | // Init 414 | init.call(this); 415 | 416 | } 417 | 418 | 419 | // Bootstrap JQuery-Plugin 420 | $.fn[pluginName] = function(options) { 421 | return this.each(function() { 422 | var 423 | opts = $.extend(true, {}, options), 424 | countimator = $(this).data(pluginName); 425 | if (!countimator) { 426 | $(this).data(pluginName, new Countimator(this, opts)); 427 | } else { 428 | countimator.setOptions(opts); 429 | } 430 | return $(this); 431 | }); 432 | }; 433 | 434 | 435 | // Style api 436 | (function() { 437 | var 438 | styles = {}; 439 | $.fn[pluginName].registerStyle = function(name, def) { 440 | styles[name] = def; 441 | }; 442 | 443 | $.fn[pluginName].getStyle = function(name) { 444 | return styles[name]; 445 | }; 446 | 447 | })(); 448 | 449 | 450 | })( jQuery, window ); -------------------------------------------------------------------------------- /src/js/jquery.countimator.wheel.js: -------------------------------------------------------------------------------- 1 | (function ( $, window ) { 2 | 3 | function arcPath(cx, cy, r, sAngle, eAngle, counterclockwise) { 4 | counterclockwise = typeof counterclockwise === 'boolean' ? counterclockwise : false; 5 | sAngle-= Math.PI / 2; 6 | eAngle-= Math.PI / 2; 7 | var 8 | d = 'M ' + cx + ', ' + cy, 9 | cxs, 10 | cys, 11 | cxe, 12 | cye; 13 | if (eAngle - sAngle === Math.PI * 2) { 14 | // Circle 15 | d+= ' m -' + r + ', 0 a ' + r + ',' + r + ' 0 1,0 ' + (r * 2) + ',0 a ' + r + ',' + r + ' 0 1,0 -' + (r * 2) + ',0'; 16 | } else { 17 | cxs = cx + Math.cos(sAngle) * r; 18 | cys = cy + Math.sin(sAngle) * r; 19 | cxe = cx + Math.cos(eAngle) * r; 20 | cye = cy + Math.sin(eAngle) * r; 21 | d+= " L" + cxs + "," + cys + 22 | " A" + r + "," + r + " 0 " + (eAngle - sAngle > Math.PI ? 1 : 0) + "," + (counterclockwise ? 0 : 1) + 23 | " " + cxe + "," + cye + " Z"; 24 | } 25 | return d; 26 | } 27 | 28 | var wheelStyle = { 29 | render: function(count, options) { 30 | var 31 | radius = 200, 32 | min = options.min ? parseFloat(options.min) : 0, 33 | max = options.max ? parseFloat(options.max) : 0, 34 | cx = radius, 35 | cy = radius, 36 | r = radius, 37 | p = (count - min) / (max - min), 38 | a = p * Math.PI * 2, 39 | d = arcPath(cx, cy, r + 1, 0, a), 40 | $graphics = $(this).find('> .counter-wheel-graphics'), 41 | 42 | $content = $(this).find('> .counter-wheel-content'); 43 | 44 | count = Math.min(max, Math.max(min, count)); 45 | 46 | if (!$content.length) { 47 | $(this).prepend($(this).wrapInner('
')); 48 | } 49 | 50 | if (!$graphics.length) { 51 | $graphics = $( 52 | '' + 53 | '' + 54 | '' 55 | ); 56 | $(this).prepend($graphics); 57 | } 58 | 59 | $graphics.find('path').attr('d', d); 60 | 61 | } 62 | }; 63 | 64 | if (!$.fn.countimator) { 65 | throw 'Include countimator script before style-plugin'; 66 | } 67 | 68 | // Add custom style to plugin registry 69 | $.fn.countimator.registerStyle('wheel', wheelStyle); 70 | 71 | })(jQuery, window); --------------------------------------------------------------------------------