1(function ( $, window ) { 2 3 var pluginName = 'countimator'; 4 5 var defaults = { 6 duration: 1000, 7 value: null, 8 max: 0, 9 lineWidth: 8, 10 valueColor: "#aaeeff", 11 maxColor: "lightgray", 12 countSelector: '.counter-count', 13 maxSelector: '.counter-max', 14 style: null, 15 template: null, 16 engine: "auto", 17 animateOnInit: true, 18 animateOnAppear: true, 19 bodyClass: 'counter-body', 20 // format options 21 decimals: 0, 22 decimalDelimiter: '.', 23 thousandDelimiter: null, 24 pad: false, 25 // appearance 26 textAlign: '', 27 verticalAlign: 'middle' 28 }; 29 30 var dataOpts = [ 31 "value", 32 "count", 33 "min", 34 "max", 35 "template", 36 "decimals", 37 "decimalDelimiter", 38 "thousandDelimiter", 39 "pad", 40 "style" 41 ]; 42 43 function formatNumber(n, decimals, decimalDelimiter, thousandDelimiter){ 44 decimals = isNaN(decimals = Math.abs(decimals)) ? 2 : decimals, 45 decimalDelimiter = typeof decimalDelimiter == 'undefined' ? "." : decimalDelimiter, 46 thousandDelimiter = typeof thousandDelimiter == 'undefined' ? "," : thousandDelimiter, 47 thousandDelimiter = typeof thousandDelimiter == 'string' ? thousandDelimiter : "", 48 s = n < 0 ? "-" : "", 49 i = parseInt(n = Math.abs(+n || 0).toFixed(decimals)) + "", 50 j = (j = i.length) > 3 ? j % 3 : 0; 51 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) : ""); 52 }; 53 54 function pad(number, length) { 55 var str = '' + number; 56 while (str.length < length) { 57 str = '0' + str; 58 } 59 return str; 60 } 61 62 function textNodes(parent) { 63 return $(parent).contents().filter(function () { 64 return this.nodeType === Node.TEXT_NODE; 65 }); 66 } 67 68 function inView(el){ 69 var top = el.getBoundingClientRect().top, left = el.getBoundingClientRect().left, rect, el = el.parentNode; 70 do { 71 rect = el.getBoundingClientRect(); 72 if (top <= rect.bottom === false && left <= rect.right === false) 73 return false; 74 el = el.parentNode; 75 } while (el != document.body); 76 // Check its within the document viewport 77 return top <= document.documentElement.clientHeight && left <= document.documentElement.clientWidth; 78 }; 79 80 function isElementInViewport (el) { 81 82 //special bonus for those using jQuery 83 if (el instanceof jQuery) { 84 el = el[0]; 85 } 86 87 var rect = el.getBoundingClientRect(); 88 89 return ( 90 rect.top >= 0 && 91 rect.left >= 0 && 92 rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */ 93 rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */ 94 ); 95 } 96 97 // request animation frame 98 // TODO: polyfill 99 var requestAnimationFrame = 100 window.mozRequestAnimationFrame || 101 window.webkitRequestAnimationFrame || 102 window.msRequestAnimationFrame || 103 window.oRequestAnimationFrame || 104 function( callback ){ 105 window.setTimeout(callback, 1000 / 60); 106 }; 107 108 109 function detectEngine() { 110 var engines = ['Handlebars', 'Mugine']; 111 for (var i = 0, engine; engine = engines[i]; i++) { 112 if (window[engine] && window[engine].compile) return engine; 113 } 114 return null; 115 } 116 117 var pluginClass = function Countimator(element, options) { 118 119 var instance = this; 120 var $element = $(element); 121 122 options = $.extend({}, defaults, options, $element.data()); 123 124 // $(dataOpts).each(function(index, value) { 125 // if (typeof $element.data(value) != 'undefined') { 126 // options[value] = $element.data(value); 127 // } 128 // }); 129 130 var canvas, ctx, container, body; 131 var startTime, startCount; 132 var animating = false; 133 134 135 // private methods 136 function init() { 137 138 var value = getOption('value'); 139 var count = getOption('count'); 140 var max = getOption('max'); 141 142 // init values 143 if (!count) { 144 var countNode = getCountNode(); 145 if (countNode) { 146 var value = getOption('value'); 147 if (typeof value != 'number') { 148 options.value = countNode.nodeValue; 149 } else { 150 options.count = countNode.nodeValue; 151 } 152 } 153 } 154 155 if (!max) { 156 var maxNode = getMaxNode(); 157 if (maxNode) { 158 options.max = maxNode.nodeValue; 159 } 160 } 161 162 // init template 163 164 var script = $element.find("script[type*='text/x-']"); 165 if (script.length) { 166 options.template = script.html(); 167 script.remove(); 168 } 169 170 // init style 171 var style = getOption('style'); 172 173 if (typeof style == "object") { 174 if (typeof style.draw == "function") { 175 176 canvas = document.createElement('canvas'); 177 if (canvas.getContext) { 178 // is supported 179 var contentNode = element.firstChild; 180 body = $('<span class="' + getOption('bodyClass') + '"></span>'); 181 $element.append(body); 182 body.append(contentNode); 183 $(canvas).insertBefore(body); 184 } 185 } 186 } 187 188 // init listeners 189 $(window).on('resize', function() { 190 resize.call(instance); 191 }); 192 193 194 $(window).on('scroll touchmove', function() {
195 if (getOption('animateOnInit') && getOption('animateOnAppear') && isElementInViewport(element)) { 196 $(window).off('scroll touchmove', arguments.callee); 197 start.call(instance); 198 } 199 }); 200 201 // render.call(this); 202 203 if (getOption('animateOnInit')) { 204 if (getOption('animateOnAppear') && isElementInViewport(element)) { 205 options.count = typeof count == 'number' ? count : 0; 206 start.call(instance); 207 } else { 208 render.call(this); 209 } 210 } else { 211 options.count = getOption('value'); 212 render.call(this); 213 } 214 215 resize.call(this); 216 217 }; 218 219 function setOption(name, value) { 220 if (name == "style") return; // style cannot be set at runtime 221 var old = options[name]; 222 options[name] = value; 223 switch (name) { 224 case 'value': 225 if (old == value) { 226 return; 227 } 228 if (typeof old != 'number') { 229 options['count'] = value; 230 render.call(this); 231 } else { 232 options['count'] = old; 233 start(); 234 } 235 break; 236 } 237 } 238 239 function getOption(name) { 240 var value = options[name]; 241 242 switch (name) { 243 244 case 'value': 245 var max = getOption('max'); 246 var min = getOption('min'); 247 var count = getOption('count'); 248 var floatValue = parseFloat(value); 249 if (!isNaN(floatValue)) { 250 floatValue = Math.max(floatValue, min); 251 value = Math.min(floatValue, max); 252 } 253 break; 254 case 'count': 255 if (!value) { 256 var max = getOption('max'); 257 var min = getOption('min'); 258 value = Math.max(value, min); 259 value = Math.min(value, max); 260 value = parseFloat(value); 261 if (isNaN(value)) { 262 value = min; 263 } 264 } 265 break; 266 case 'max': 267 value = parseFloat(value ? value : options.value); 268 break; 269 case 'min': 270 if (!value) { 271 value = 0; 272 } 273 break; 274 case 'style': 275 value = $.fn.countimator.getStyle(value); 276 break; 277 case 'template': 278 // try to find by selector 279 try { 280 value = $(value).html(); 281 } catch(e) { 282 // not a valid selector 283 } 284 break; 285 } 286 return value; 287 } 288 289 function resize() { 290 291 292 293 var $body = $element.find("." + this.getOption('bodyClass')); 294 295 // reset element 296 $element.css({ 297 textAlign: '' 298 }); 299 300 var textAlign = this.getOption('textAlign') || $element.css('text-align'); 301 var verticalAlign = this.getOption('verticalAlign'); 302 303 $element.css({ 304 textAlign: textAlign 305 }); 306 307 // TODO: replace canvas with background 308 309 if (canvas) { 310 311 $element.css({ 312 position: position != "static" ? position : 'relative' 313 }); 314 315 $(canvas).css({ 316 position: 'absolute', 317 top: 0, 318 left: 0 319 }); 320 321 // reset body 322 $body.css({ 323 position: 'absolute', 324 top: "", 325 bottom: "", 326 left: "", 327 right: "", 328 textAlign: "", 329 display: 'none' 330 }); 331 332 333 334 } 335 336 337 var position = $element.css('position'); 338 var display = $element.css('display'); 339 340 var width = $element.width(); 341 var height = $element.height(); 342 343 var outerWidth = $element.outerWidth(false); 344 var outerHeight = $element.outerHeight(false); 345 346 347 if (canvas) { 348 // vertical-align 349 var paddingLeft = parseFloat($element.css('padding-left')); 350 var paddingRight = parseFloat($element.css('padding-right')); 351 var paddingTop = parseFloat($element.css('padding-top')); 352 var paddingBottom = parseFloat($element.css('padding-top')); 353 var top, bottom; 354 if (verticalAlign == 'justify') { 355 top = paddingTop; 356 bottom = paddingBottom; 357 } else { 358 var va = verticalAlign == 'top' ? 0 : verticalAlign == 'bottom' ? 1 : 0.5; 359 top = paddingTop + ( height - $body.outerHeight(false) ) * va; 360 bottom = 'auto'; 361 } 362 363 $body.css({ 364 position: 'absolute', 365 top: top, 366 bottom: bottom, 367 left: paddingLeft, 368 right: paddingRight, 369 textAlign: textAlign, 370 display: '' 371 }); 372 373 } 374 375 var style = getOption('style'); 376 if (style && style.resize) { 377 style.resize.call(instance, element, width, height); 378 } 379 380 if (canvas) { 381 // resize canvas 382 canvas.width = $element.innerWidth();
383 canvas.height = $element.innerHeight(); 384 } 385 386 draw.call(this); 387 388 }; 389 390 function animate(value) { 391 options.value = value; 392 if (!animating) { 393 start(); 394 } else { 395 // TODO: stretch duration 396 } 397 } 398 399 function start() { 400 startTime = new Date().getTime(); 401 startCount = getOption('count'); 402 animating = true; 403 requestAnimationFrame(step); 404 } 405 406 function getCountNode(count) { 407 var countElement = $element.find(options.countSelector)[0]; 408 var textNode = textNodes(countElement || element)[0]; 409 return textNode; 410 } 411 412 function getMaxNode(count) { 413 var maxElement = $element.find(options.maxSelector)[0]; 414 if (maxElement) { 415 return textNodes(maxElement)[0]; 416 } 417 return null; 418 } 419 420 function getFormattedValue(value) { 421 422 // format number 423 var decimals = getOption('decimals'); 424 var decimalDelimiter = getOption('decimalDelimiter'); 425 var thousandDelimiter = getOption('thousandDelimiter'); 426 427 string = formatNumber(value, decimals, decimalDelimiter, thousandDelimiter); 428 429 // pad 430 string = pad(string, getOption('pad')); 431 432 return string; 433 } 434 435 436 function render() { 437 438 var max = getOption('max'); 439 var min = getOption('min'); 440 var value = getOption('value'); 441 var count = getOption('count'); 442 443 var formattedCount = getFormattedValue(count); 444 var formattedValue = getFormattedValue(value); 445 var formattedMax = getFormattedValue(max); 446 var formattedMin = getFormattedValue(min); 447 448 var engine = getOption('engine'); 449 engine = engine && engine != 'auto' ? engine : detectEngine(); 450 engine = typeof engine == "string" ? window[engine] : engine; 451 452 var template = getOption('template'); 453 var string; 454 455 if (engine && template) { 456 457 // template engine 458 var tmpl = engine.compile(template); 459 460 if (tmpl) { 461 var tmplData = $.extend({}, options, {count: formattedCount, value: formattedValue, max: formattedMax, min: formattedMin}); 462 string = tmpl(tmplData); 463 } 464 var div = document.createElement('div'); 465 div.innerHTML = string; 466 var nodeList = div.childNodes; 467 468 if (body) { 469 $(body).empty(); 470 $(body).append(nodeList); 471 } else { 472 $(element).contents().not(container).remove(); 473 $(element).append(nodeList); 474 } 475 476 } else { 477 478 // classic approach without template engine 479 480 string = formattedCount; 481 482 // set count 483 var countNode = getCountNode(); 484 if (countNode) { 485 countNode.nodeValue = formattedCount; 486 } 487 // set max (there will be no max-node when using templates) 488 var maxNode = getMaxNode(); 489 if (maxNode) { 490 maxNode.nodeValue = formattedMax; 491 } 492 493 if (!countNode && !maxNode) { 494 element.innerHTML = formattedCount; 495 } 496 497 } 498 499 var style = getOption('style'); 500 if (style && style.render) { 501 style.render.call(instance, count); 502 } 503 504 // draw 505 draw.call(this); 506 507 } 508 509 function draw() { 510 var count = getOption('count'); 511 var style = getOption('style'); 512 if (style && style.draw && canvas && canvas.getContext) { 513 var ctx = canvas.getContext("2d"); 514 ctx.clearRect(0, 0, canvas.width, canvas.height); 515 style.draw.call(instance, canvas, count); 516 } 517 } 518 519 function step() { 520 521 var duration = getOption('duration'); 522 var max = getOption('max'); 523 var value = getOption('value'); 524 525 var currentTime = new Date().getTime(); 526 var endTime = startTime + duration; 527 528 var currentStep = Math.min((duration - (endTime - currentTime)) / duration, 1); 529 530 var count = startCount + currentStep * (value - startCount); 531 532 options.count = count; 533 534 render.call(this); 535 536 if (currentStep < 1 && animating) { 537 538 requestAnimationFrame(step); 539 540 } else { 541 542 end.call(this); 543 } 544 545 } 546 547 548 function end() { 549 animating = false;
550 } 551 552 553 554 // public methods 555 556 this.resize = function() { 557 resize.call(this); 558 }; 559 560 this.getOption = function(name) { 561 return getOption.call(this, name); 562 }; 563 564 this.setOption = function(name, value) { 565 setOption.call(this, name, value); 566 }; 567 568 this.animate = function(value) { 569 animate.call(this, value); 570 }; 571 572 // call init 573 init.call(this); 574 575 }; 576 577 578 // bootstrap plugin 579 580 $.fn[pluginName] = function(options) { 581 582 return this.each(function() { 583 584 if (!$(this).data(pluginName)) { 585 $(this).data(pluginName, new pluginClass(this, options)); 586 } 587 588 return $(this); 589 590 }); 591 }; 592 593 594 // style api 595 596 var styles = {}; 597 598 $.fn[pluginName].registerStyle = function(name, def) { 599 styles[name] = def; 600 }; 601 602 $.fn[pluginName].getStyle = function(name) { 603 return styles[name]; 604 }; 605 606 607})( jQuery, window );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.