├── .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 |
117 | Want more?
118 |
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 | Click me!
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 | Name
318 | Description
319 |
320 |
321 |
322 |
323 | animateOnAppear
324 | Specifies whether to start animation when scrolled into view. Defaults to `true`
325 |
326 |
327 | animateOnInit
328 | Specifies whether to start animation when initialized. Defaults to `true`
329 |
330 |
331 | complete
332 | Callback function to be executed when animation completes.
333 |
334 |
335 | count
336 | Current animation count. Updated on step. Defaults to `0`
337 |
338 |
339 | countSelector
340 | Specifies the selector of count element. Defaults to `'.counter-count'`
341 |
342 |
343 | decimals
344 | Specifies the number of decimals for number formatting. Defaults to `0`
345 |
346 |
347 | decimalDelimiter
348 | Specifies a decimal separator for number formatting. Defaults to `.`
349 |
350 |
351 | duration
352 | Specifies the animation duration in milliseconds. Defaults to `1400`
353 |
354 |
355 | engine
356 | Specifies the template engine to use. `Handlebars` used, if defined
357 |
358 |
359 | max
360 | Specifies the maximum value of the animation. Defaults to `0`
361 |
362 |
363 | maxSelector
364 | Specifies the selector of maximum element. Defaults to `'.counter-max'`
365 |
366 |
367 | min
368 | Specifies the minimum value of the animation. Defaults to `null`
369 |
370 |
371 | pad
372 | Specifies the number of digits to be padded with leading zeros
373 |
374 |
375 | start
376 | Callback function to be executed when animation starts.
377 |
378 |
379 | step
380 | Callback function to be executed when animation on animation step.
381 |
382 |
383 | style
384 | Specifies a custom style. Either provide a string identifier of a predefined style or an object containing a `render`-method.
385 |
386 |
387 | template
388 | Either specifies an inline-template or a selector for dom-template.
389 |
390 |
391 | thousandDelimiter
392 | Specifies a thousand delimiter for number formatting. Defaults to `null`
393 |
394 |
395 | value
396 | Specifies the target value of the animation. Defaults to `null`
397 |
398 |
399 |
--------------------------------------------------------------------------------
/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);
--------------------------------------------------------------------------------