├── .gitignore ├── docs ├── images │ └── odometer.gif ├── welcome │ ├── onepage-scroll.css │ ├── landing-page.coffee │ ├── landing-page.js │ ├── landing-page.css │ ├── onepage-scroll.js │ └── index.html ├── api │ └── themes.md └── intro.md ├── .hsdoc ├── config.rb ├── sass ├── odometer-theme-minimal.sass ├── odometer-theme-default.sass ├── odometer-theme-digital.sass ├── odometer-theme-plaza.sass ├── odometer-theme-train-station.sass ├── odometer-theme-car.sass ├── odometer-theme-slot-machine.sass └── _mixins.sass ├── README.md ├── component.json ├── test ├── demo.html └── performance.html ├── package.json ├── bower.json ├── LICENSE ├── Gruntfile.coffee ├── themes ├── odometer-theme-minimal.css ├── odometer-theme-default.css ├── odometer-theme-plaza.css ├── odometer-theme-digital.css ├── odometer-theme-train-station.css ├── odometer-theme-car.css └── odometer-theme-slot-machine.css ├── odometer.min.js ├── odometer.coffee └── odometer.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .sass-cache/ -------------------------------------------------------------------------------- /docs/images/odometer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HubSpot/odometer/HEAD/docs/images/odometer.gif -------------------------------------------------------------------------------- /.hsdoc: -------------------------------------------------------------------------------- 1 | title: "Odometer" 2 | source: "odometer.coffee" 3 | examples: "**/*.md" 4 | assets: "{docs/welcome/*,odometer.js,themes/*}" 5 | -------------------------------------------------------------------------------- /config.rb: -------------------------------------------------------------------------------- 1 | preferred_syntax = :sass 2 | css_dir = './themes' 3 | sass_dir = './sass' 4 | output_style = :expanded 5 | environment = :production -------------------------------------------------------------------------------- /sass/odometer-theme-minimal.sass: -------------------------------------------------------------------------------- 1 | @import compass/css3 2 | @import mixins 3 | 4 | $themeName: "odometer-theme-minimal" 5 | 6 | +spinning-odometer($themeName) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Odometer 2 | ======== 3 | 4 | Odometer is a Javascript and CSS library for smoothly transitioning numbers. 5 | 6 | ### [Overview](http://github.hubspot.com/odometer/docs/welcome) 7 | ### [Docs and Demo](http://github.hubspot.com/odometer) 8 | -------------------------------------------------------------------------------- /sass/odometer-theme-default.sass: -------------------------------------------------------------------------------- 1 | @import compass/css3 2 | @import mixins 3 | 4 | $themeName: "odometer-theme-default" 5 | 6 | +spinning-odometer($themeName) 7 | 8 | .odometer.odometer-auto-theme, .odometer#{"." + $themeName} 9 | font-family: "Helvetica Neue", sans-serif 10 | line-height: 1.1em 11 | 12 | .odometer-value 13 | text-align: center -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "odometer", 3 | "version": "0.4.8", 4 | "repo": "hubspot/odometer", 5 | "description": "Transition numbers with ease", 6 | "keywords": [ 7 | "odometer", 8 | "car", 9 | "number", 10 | "transition", 11 | "animation", 12 | "slot", 13 | "machine", 14 | "turnstile", 15 | "javascript", 16 | "client-side" 17 | ], 18 | "styles": ["themes/odometer-theme-default.css"], 19 | "scripts": [ 20 | "odometer.js" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /test/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 13 | 14 | 15 | 16 |
3252
17 | 18 | 23 | -------------------------------------------------------------------------------- /sass/odometer-theme-digital.sass: -------------------------------------------------------------------------------- 1 | @import compass/css3 2 | @import mixins 3 | @import url("//fonts.googleapis.com/css?family=Wallpoet") 4 | 5 | $themeName: "odometer-theme-digital" 6 | $green: #8bf5a5 7 | 8 | +spinning-odometer($themeName) 9 | 10 | .odometer.odometer-auto-theme, .odometer#{"." + $themeName} 11 | +background-image(radial-gradient(rgba($green, 0.4), #000)) 12 | background-color: #000 13 | font-family: "Wallpoet", monospace 14 | padding: 0 .2em 15 | line-height: 1.1em 16 | color: $green 17 | 18 | .odometer-digit + .odometer-digit 19 | margin-left: .1em -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "odometer", 3 | "version": "0.4.8", 4 | "description": "Transition numbers with ease", 5 | "main": "odometer.js", 6 | "authors": [ 7 | "Adam Schwartz ", 8 | "Zack Bloom " 9 | ], 10 | "license": "MIT", 11 | "devDependencies": { 12 | "grunt-contrib-coffee": "~0.7.0", 13 | "coffee-script": "~1.6.3", 14 | "grunt-contrib-uglify": "~0.2.4", 15 | "grunt-cli": "~0.1.9", 16 | "grunt": "~0.4.1", 17 | "grunt-contrib-watch": "~0.5.3", 18 | "grunt-contrib-compass": "~0.5.0", 19 | "color": "~0.4.4" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "odometer", 3 | "main": "odometer.js", 4 | "version": "0.4.8", 5 | "homepage": "http://github.hubspot.com/odometer/docs/welcome", 6 | "authors": [ 7 | "Zack Bloom ", 8 | "Adam Schwartz " 9 | ], 10 | "description": "Transition numbers with ease", 11 | "keywords": [ 12 | "odometer", 13 | "car", 14 | "number", 15 | "transition", 16 | "animation", 17 | "slot", 18 | "machine", 19 | "turnstile", 20 | "javascript", 21 | "client-side" 22 | ], 23 | "license": "MIT", 24 | "ignore": [ 25 | "**/.*", 26 | "node_modules", 27 | "bower_components" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /sass/odometer-theme-plaza.sass: -------------------------------------------------------------------------------- 1 | @import compass/css3 2 | @import mixins 3 | 4 | $themeName: "odometer-theme-plaza" 5 | $digitPadding: .03em 6 | 7 | +spinning-odometer($themeName) 8 | 9 | .odometer.odometer-auto-theme, .odometer#{"." + $themeName} 10 | +border-radius(.15em) 11 | background-color: #f0f8ff 12 | font-family: "Helvetica Neue", sans-serif 13 | font-weight: 100 14 | padding: 0 .12em 15 | line-height: 1.2em 16 | font-size: 1.2em 17 | background-size: 16px 16px 18 | 19 | .odometer-digit 20 | +border-radius(.1em) 21 | padding: 0 $digitPadding 22 | color: #648baf 23 | 24 | .odometer-digit-inner 25 | left: $digitPadding 26 | -------------------------------------------------------------------------------- /sass/odometer-theme-train-station.sass: -------------------------------------------------------------------------------- 1 | @import compass/css3 2 | @import mixins 3 | @import url("//fonts.googleapis.com/css?family=Economica") 4 | 5 | $themeName: "odometer-theme-train-station" 6 | $digitPadding: .15em 7 | 8 | +spinning-odometer($themeName) 9 | 10 | .odometer.odometer-auto-theme, .odometer#{"." + $themeName} 11 | font-family: "Economica", sans-serif 12 | 13 | .odometer-digit 14 | +inline-block 15 | +border-radius(.1em) 16 | +background-image(linear-gradient(top, #111 0%, #111 35%, #333 55%, #111 55%, #111 100%)) 17 | background-color: #222 18 | padding: 0 $digitPadding 19 | color: #fff 20 | 21 | + .odometer-digit 22 | margin-left: .1em 23 | 24 | .odometer-digit-inner 25 | left: $digitPadding -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 HubSpot, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | Path = require('path') 2 | fs = require('fs') 3 | 4 | module.exports = (grunt) -> 5 | grunt.initConfig 6 | pkg: grunt.file.readJSON("package.json") 7 | 8 | coffee: 9 | compile: 10 | files: 11 | 'odometer.js': 'odometer.coffee' 12 | 'docs/welcome/landing-page.js': 'docs/welcome/landing-page.coffee' 13 | 14 | watch: 15 | coffee: 16 | files: ['odometer.coffee', 'docs/welcome/landing-page.coffee', 'sass/*'] 17 | tasks: ["coffee", "uglify", "compass"] 18 | 19 | uglify: 20 | options: 21 | banner: "/*! <%= pkg.name %> <%= pkg.version %> */\n" 22 | 23 | dist: 24 | src: 'odometer.js' 25 | dest: 'odometer.min.js' 26 | 27 | compass: 28 | dist: 29 | options: 30 | sassDir: 'sass' 31 | cssDir: 'themes' 32 | 33 | grunt.loadNpmTasks 'grunt-contrib-watch' 34 | grunt.loadNpmTasks 'grunt-contrib-uglify' 35 | grunt.loadNpmTasks 'grunt-contrib-coffee' 36 | grunt.loadNpmTasks 'grunt-contrib-compass' 37 | 38 | grunt.registerTask 'default', ['coffee', 'uglify', 'compass'] 39 | -------------------------------------------------------------------------------- /sass/odometer-theme-car.sass: -------------------------------------------------------------------------------- 1 | @import compass/css3 2 | @import mixins 3 | @import url("//fonts.googleapis.com/css?family=Arimo") 4 | 5 | $themeName: "odometer-theme-car" 6 | $padding: .15em 7 | $borderRadius: .2em 8 | 9 | +spinning-odometer($themeName) 10 | 11 | .odometer.odometer-auto-theme, .odometer#{"." + $themeName} 12 | +border-radius(.34em) 13 | font-family: "Arimo", monospace 14 | padding: $padding 15 | background: #000 16 | color: #eee0d3 17 | 18 | .odometer-digit 19 | +box-shadow(inset 0 0 .3em rgba(0, 0, 0, 0.8)) 20 | +background-image(linear-gradient(top, #333 0%, #333 40%, #101010 60%, #333 80%, #333 100%)) 21 | padding: 0 $padding 22 | 23 | &:first-child 24 | +border-radius($borderRadius 0 0 $borderRadius) 25 | 26 | &:last-child 27 | +border-radius(0 $borderRadius $borderRadius 0) 28 | +background-image(linear-gradient(top, #eee0d3 0%, #eee0d3 40%, #bbaa9a 60%, #eee0d3 80%, #eee0d3 100%)) 29 | background-color: #eee0d3 30 | color: #000 31 | 32 | .odometer-digit-inner 33 | left: $padding 34 | 35 | &.odometer-animating-up .odometer-ribbon-inner, &.odometer-animating-down.odometer-animating .odometer-ribbon-inner 36 | -webkit-transition-timing-function: linear 37 | -moz-transition-timing-function: linear 38 | -ms-transition-timing-function: linear 39 | -o-transition-timing-function: linear 40 | transition-timing-function: linear -------------------------------------------------------------------------------- /sass/odometer-theme-slot-machine.sass: -------------------------------------------------------------------------------- 1 | @import compass/css3 2 | @import mixins 3 | @import url("//fonts.googleapis.com/css?family=Rye") 4 | 5 | $themeName: "odometer-theme-slot-machine" 6 | $spacing: .15em 7 | $borderRadius: .2em 8 | 9 | +spinning-odometer($themeName) 10 | 11 | .odometer.odometer-auto-theme, .odometer#{"." + $themeName} 12 | +border-radius(.34em) 13 | +background-image(linear-gradient(#ff0, #ffa500)) 14 | background-color: #fc0 15 | font-family: "Rye", monospace 16 | padding: $spacing 17 | color: #f80000 18 | line-height: 1.35em 19 | border: .03em solid #000 20 | -webkit-text-stroke: .05em #000 21 | 22 | .odometer-digit 23 | +box-shadow(inset 0 0 .1em rgba(0, 0, 0, 0.5), 0 0 0 .03em #fff, 0 0 0 .05em rgba(0, 0, 0, 0.2)) 24 | +border-radius(.2em) 25 | +background-image(linear-gradient(top, #ccc 0%, #fff 20%, #fff 80%, #ccc 100%)) 26 | border: .03em solid #444 27 | padding: .1em $spacing 0 28 | 29 | &:first-child 30 | +box-shadow(inset .05em 0 .1em rgba(0, 0, 0, 0.5), 0 0 0 .03em #fff, 0 0 0 .05em rgba(0, 0, 0, 0.2)) 31 | 32 | &:last-child 33 | +box-shadow(inset -.05em 0 .1em rgba(0, 0, 0, 0.5), 0 0 0 .03em #fff, 0 0 0 .05em rgba(0, 0, 0, 0.2)) 34 | 35 | + .odometer-digit 36 | margin-left: $spacing 37 | 38 | .odometer-digit-inner 39 | padding-top: .08em 40 | 41 | .odometer-digit-inner, .odometer-value.odometer-last-value 42 | left: 0 43 | right: 0 44 | text-align: center -------------------------------------------------------------------------------- /test/performance.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 18 | 19 |
20 |
21 |
22 | 23 | 32 | 33 |
34 | 35 | 80 | -------------------------------------------------------------------------------- /docs/welcome/onepage-scroll.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin: 0; 3 | overflow: hidden; 4 | -webkit-transition: opacity 400ms; 5 | -moz-transition: opacity 400ms; 6 | transition: opacity 400ms; 7 | } 8 | 9 | body, .onepage-wrapper, html { 10 | display: block; 11 | position: static; 12 | padding: 0; 13 | width: 100%; 14 | height: 100%; 15 | } 16 | 17 | .main { 18 | height: 100%; 19 | } 20 | 21 | .onepage-wrapper { 22 | width: 100%; 23 | height: 100%; 24 | display: block; 25 | position: relative; 26 | padding: 0; 27 | -webkit-transform-style: preserve-3d; 28 | } 29 | 30 | .onepage-wrapper .section { 31 | width: 100%; 32 | height: 100%; 33 | } 34 | 35 | .onepage-pagination { 36 | position: absolute; 37 | margin: auto; 38 | right: 10px; 39 | left: auto; 40 | top: 0; 41 | bottom: 0; 42 | height: 190px; 43 | z-index: 5; 44 | list-style: none; 45 | padding: 0; 46 | } 47 | 48 | .onepage-pagination li { 49 | padding: 0; 50 | text-align: center; 51 | } 52 | 53 | .onepage-pagination li a{ 54 | padding: 10px; 55 | width: 4px; 56 | height: 4px; 57 | display: block; 58 | 59 | } 60 | .onepage-pagination li a:before{ 61 | content: ''; 62 | position: absolute; 63 | width: 4px; 64 | height: 4px; 65 | background: rgba(0,0,0,0.85); 66 | border-radius: 10px; 67 | -webkit-border-radius: 10px; 68 | -moz-border-radius: 10px; 69 | } 70 | 71 | .onepage-pagination li a.active:before{ 72 | width: 10px; 73 | height: 10px; 74 | background: none; 75 | border: 1px solid black; 76 | margin-top: -4px; 77 | left: 8px; 78 | } 79 | 80 | body.viewing-page-2 .onepage-pagination li a:before { background-color: #444; } 81 | body.viewing-page-2 .onepage-pagination li a.active:before { border-color: #444; background: none; } 82 | 83 | body.viewing-page-3 .onepage-pagination li a:before { background-color: #333; } 84 | body.viewing-page-3 .onepage-pagination li a.active:before { border-color: #333; background: none; } 85 | body.viewing-page-3 .social-sharing-wrapper { -webkit-filter: grayscale(1) sepia(1) hue-rotate(-47deg); } 86 | body.viewing-page-3 .social-sharing-wrapper:hover { -webkit-filter: none; } 87 | 88 | body.viewing-page-4 .onepage-pagination li a:before { background-color: #8bf5a5; } 89 | body.viewing-page-4 .onepage-pagination li a.active:before { border-color: #8bf5a5; background: none; } 90 | body.viewing-page-4 .social-sharing-wrapper { -webkit-filter: grayscale(1) contrast(1.3) invert(1); } 91 | body.viewing-page-4 .social-sharing-wrapper:hover { -webkit-filter: none; } -------------------------------------------------------------------------------- /sass/_mixins.sass: -------------------------------------------------------------------------------- 1 | @import compass/css3 2 | 3 | =spinning-odometer($themeName) 4 | .odometer.odometer-auto-theme, .odometer#{"." + $themeName} 5 | +inline-block 6 | position: relative 7 | 8 | .odometer-digit 9 | +inline-block 10 | position: relative 11 | 12 | .odometer-digit-spacer 13 | +inline-block 14 | visibility: hidden 15 | 16 | .odometer-digit-inner 17 | text-align: left 18 | display: block 19 | position: absolute 20 | top: 0 21 | left: 0 22 | right: 0 23 | bottom: 0 24 | overflow: hidden 25 | 26 | .odometer-ribbon 27 | display: block 28 | 29 | .odometer-ribbon-inner 30 | display: block 31 | -webkit-backface-visibility: hidden 32 | 33 | .odometer-value 34 | display: block 35 | -webkit-transform: translateZ(0) 36 | 37 | &.odometer-last-value 38 | position: absolute 39 | 40 | &.odometer-animating-up 41 | 42 | .odometer-ribbon-inner 43 | -webkit-transition: -webkit-transform 2s 44 | -moz-transition: -moz-transform 2s 45 | -ms-transition: -ms-transform 2s 46 | -o-transition: -o-transform 2s 47 | transition: transform 2s 48 | 49 | &.odometer-animating .odometer-ribbon-inner 50 | -webkit-transform: translateY(-100%) 51 | -moz-transform: translateY(-100%) 52 | -ms-transform: translateY(-100%) 53 | -o-transform: translateY(-100%) 54 | transform: translateY(-100%) 55 | 56 | &.odometer-animating-down 57 | 58 | .odometer-ribbon-inner 59 | -webkit-transform: translateY(-100%) 60 | -moz-transform: translateY(-100%) 61 | -ms-transform: translateY(-100%) 62 | -o-transform: translateY(-100%) 63 | transform: translateY(-100%) 64 | 65 | &.odometer-animating .odometer-ribbon-inner 66 | -webkit-transition: -webkit-transform 2s 67 | -moz-transition: -moz-transform 2s 68 | -ms-transition: -ms-transform 2s 69 | -o-transition: -o-transform 2s 70 | transition: transform 2s 71 | 72 | -webkit-transform: translateY(0) 73 | -moz-transform: translateY(0) 74 | -ms-transform: translateY(0) 75 | -o-transform: translateY(0) 76 | transform: translateY(0) -------------------------------------------------------------------------------- /themes/odometer-theme-minimal.css: -------------------------------------------------------------------------------- 1 | .odometer.odometer-auto-theme, .odometer.odometer-theme-minimal { 2 | display: inline-block; 3 | vertical-align: middle; 4 | *vertical-align: auto; 5 | *zoom: 1; 6 | *display: inline; 7 | position: relative; 8 | } 9 | .odometer.odometer-auto-theme .odometer-digit, .odometer.odometer-theme-minimal .odometer-digit { 10 | display: inline-block; 11 | vertical-align: middle; 12 | *vertical-align: auto; 13 | *zoom: 1; 14 | *display: inline; 15 | position: relative; 16 | } 17 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-spacer, .odometer.odometer-theme-minimal .odometer-digit .odometer-digit-spacer { 18 | display: inline-block; 19 | vertical-align: middle; 20 | *vertical-align: auto; 21 | *zoom: 1; 22 | *display: inline; 23 | visibility: hidden; 24 | } 25 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-inner, .odometer.odometer-theme-minimal .odometer-digit .odometer-digit-inner { 26 | text-align: left; 27 | display: block; 28 | position: absolute; 29 | top: 0; 30 | left: 0; 31 | right: 0; 32 | bottom: 0; 33 | overflow: hidden; 34 | } 35 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon, .odometer.odometer-theme-minimal .odometer-digit .odometer-ribbon { 36 | display: block; 37 | } 38 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon-inner, .odometer.odometer-theme-minimal .odometer-digit .odometer-ribbon-inner { 39 | display: block; 40 | -webkit-backface-visibility: hidden; 41 | } 42 | .odometer.odometer-auto-theme .odometer-digit .odometer-value, .odometer.odometer-theme-minimal .odometer-digit .odometer-value { 43 | display: block; 44 | -webkit-transform: translateZ(0); 45 | } 46 | .odometer.odometer-auto-theme .odometer-digit .odometer-value.odometer-last-value, .odometer.odometer-theme-minimal .odometer-digit .odometer-value.odometer-last-value { 47 | position: absolute; 48 | } 49 | .odometer.odometer-auto-theme.odometer-animating-up .odometer-ribbon-inner, .odometer.odometer-theme-minimal.odometer-animating-up .odometer-ribbon-inner { 50 | -webkit-transition: -webkit-transform 2s; 51 | -moz-transition: -moz-transform 2s; 52 | -ms-transition: -ms-transform 2s; 53 | -o-transition: -o-transform 2s; 54 | transition: transform 2s; 55 | } 56 | .odometer.odometer-auto-theme.odometer-animating-up.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-minimal.odometer-animating-up.odometer-animating .odometer-ribbon-inner { 57 | -webkit-transform: translateY(-100%); 58 | -moz-transform: translateY(-100%); 59 | -ms-transform: translateY(-100%); 60 | -o-transform: translateY(-100%); 61 | transform: translateY(-100%); 62 | } 63 | .odometer.odometer-auto-theme.odometer-animating-down .odometer-ribbon-inner, .odometer.odometer-theme-minimal.odometer-animating-down .odometer-ribbon-inner { 64 | -webkit-transform: translateY(-100%); 65 | -moz-transform: translateY(-100%); 66 | -ms-transform: translateY(-100%); 67 | -o-transform: translateY(-100%); 68 | transform: translateY(-100%); 69 | } 70 | .odometer.odometer-auto-theme.odometer-animating-down.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-minimal.odometer-animating-down.odometer-animating .odometer-ribbon-inner { 71 | -webkit-transition: -webkit-transform 2s; 72 | -moz-transition: -moz-transform 2s; 73 | -ms-transition: -ms-transform 2s; 74 | -o-transition: -o-transform 2s; 75 | transition: transform 2s; 76 | -webkit-transform: translateY(0); 77 | -moz-transform: translateY(0); 78 | -ms-transform: translateY(0); 79 | -o-transform: translateY(0); 80 | transform: translateY(0); 81 | } 82 | -------------------------------------------------------------------------------- /docs/api/themes.md: -------------------------------------------------------------------------------- 1 | ## Themes 2 | 3 | To use a builtin theme, simply include the theme style sheet: 4 | 5 | ```html 6 | 7 | ``` 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
NameTheme
Default`default`
Minimal`minimal`
Car`car`
Plaza`plaza`
Slot Machine`slot-machine`
Train Station`train-station`
Digital`digital`
25 | 26 | ### Multiple Themes on One Page 27 | 28 | If you need to use multipled Odometer themes on a single page, do the following. 29 | 30 | ```javascript 31 | odometerOptions = { auto: false }; // Disables auto-initialization 32 | 33 | // For each odometer, initialize with the theme passed in: 34 | var odometer = new Odometer({ el: $('.odometer')[0], value: 123, theme: 'car' }); 35 | odometer.render(); 36 | ``` 37 | 38 | 39 |

40 | 41 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 73 | -------------------------------------------------------------------------------- /themes/odometer-theme-default.css: -------------------------------------------------------------------------------- 1 | .odometer.odometer-auto-theme, .odometer.odometer-theme-default { 2 | display: inline-block; 3 | vertical-align: middle; 4 | *vertical-align: auto; 5 | *zoom: 1; 6 | *display: inline; 7 | position: relative; 8 | } 9 | .odometer.odometer-auto-theme .odometer-digit, .odometer.odometer-theme-default .odometer-digit { 10 | display: inline-block; 11 | vertical-align: middle; 12 | *vertical-align: auto; 13 | *zoom: 1; 14 | *display: inline; 15 | position: relative; 16 | } 17 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-spacer, .odometer.odometer-theme-default .odometer-digit .odometer-digit-spacer { 18 | display: inline-block; 19 | vertical-align: middle; 20 | *vertical-align: auto; 21 | *zoom: 1; 22 | *display: inline; 23 | visibility: hidden; 24 | } 25 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-inner, .odometer.odometer-theme-default .odometer-digit .odometer-digit-inner { 26 | text-align: left; 27 | display: block; 28 | position: absolute; 29 | top: 0; 30 | left: 0; 31 | right: 0; 32 | bottom: 0; 33 | overflow: hidden; 34 | } 35 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon, .odometer.odometer-theme-default .odometer-digit .odometer-ribbon { 36 | display: block; 37 | } 38 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon-inner, .odometer.odometer-theme-default .odometer-digit .odometer-ribbon-inner { 39 | display: block; 40 | -webkit-backface-visibility: hidden; 41 | } 42 | .odometer.odometer-auto-theme .odometer-digit .odometer-value, .odometer.odometer-theme-default .odometer-digit .odometer-value { 43 | display: block; 44 | -webkit-transform: translateZ(0); 45 | } 46 | .odometer.odometer-auto-theme .odometer-digit .odometer-value.odometer-last-value, .odometer.odometer-theme-default .odometer-digit .odometer-value.odometer-last-value { 47 | position: absolute; 48 | } 49 | .odometer.odometer-auto-theme.odometer-animating-up .odometer-ribbon-inner, .odometer.odometer-theme-default.odometer-animating-up .odometer-ribbon-inner { 50 | -webkit-transition: -webkit-transform 2s; 51 | -moz-transition: -moz-transform 2s; 52 | -ms-transition: -ms-transform 2s; 53 | -o-transition: -o-transform 2s; 54 | transition: transform 2s; 55 | } 56 | .odometer.odometer-auto-theme.odometer-animating-up.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-default.odometer-animating-up.odometer-animating .odometer-ribbon-inner { 57 | -webkit-transform: translateY(-100%); 58 | -moz-transform: translateY(-100%); 59 | -ms-transform: translateY(-100%); 60 | -o-transform: translateY(-100%); 61 | transform: translateY(-100%); 62 | } 63 | .odometer.odometer-auto-theme.odometer-animating-down .odometer-ribbon-inner, .odometer.odometer-theme-default.odometer-animating-down .odometer-ribbon-inner { 64 | -webkit-transform: translateY(-100%); 65 | -moz-transform: translateY(-100%); 66 | -ms-transform: translateY(-100%); 67 | -o-transform: translateY(-100%); 68 | transform: translateY(-100%); 69 | } 70 | .odometer.odometer-auto-theme.odometer-animating-down.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-default.odometer-animating-down.odometer-animating .odometer-ribbon-inner { 71 | -webkit-transition: -webkit-transform 2s; 72 | -moz-transition: -moz-transform 2s; 73 | -ms-transition: -ms-transform 2s; 74 | -o-transition: -o-transform 2s; 75 | transition: transform 2s; 76 | -webkit-transform: translateY(0); 77 | -moz-transform: translateY(0); 78 | -ms-transform: translateY(0); 79 | -o-transform: translateY(0); 80 | transform: translateY(0); 81 | } 82 | 83 | .odometer.odometer-auto-theme, .odometer.odometer-theme-default { 84 | font-family: "Helvetica Neue", sans-serif; 85 | line-height: 1.1em; 86 | } 87 | .odometer.odometer-auto-theme .odometer-value, .odometer.odometer-theme-default .odometer-value { 88 | text-align: center; 89 | } 90 | -------------------------------------------------------------------------------- /themes/odometer-theme-plaza.css: -------------------------------------------------------------------------------- 1 | .odometer.odometer-auto-theme, .odometer.odometer-theme-plaza { 2 | display: inline-block; 3 | vertical-align: middle; 4 | *vertical-align: auto; 5 | *zoom: 1; 6 | *display: inline; 7 | position: relative; 8 | } 9 | .odometer.odometer-auto-theme .odometer-digit, .odometer.odometer-theme-plaza .odometer-digit { 10 | display: inline-block; 11 | vertical-align: middle; 12 | *vertical-align: auto; 13 | *zoom: 1; 14 | *display: inline; 15 | position: relative; 16 | } 17 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-spacer, .odometer.odometer-theme-plaza .odometer-digit .odometer-digit-spacer { 18 | display: inline-block; 19 | vertical-align: middle; 20 | *vertical-align: auto; 21 | *zoom: 1; 22 | *display: inline; 23 | visibility: hidden; 24 | } 25 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-inner, .odometer.odometer-theme-plaza .odometer-digit .odometer-digit-inner { 26 | text-align: left; 27 | display: block; 28 | position: absolute; 29 | top: 0; 30 | left: 0; 31 | right: 0; 32 | bottom: 0; 33 | overflow: hidden; 34 | } 35 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon, .odometer.odometer-theme-plaza .odometer-digit .odometer-ribbon { 36 | display: block; 37 | } 38 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon-inner, .odometer.odometer-theme-plaza .odometer-digit .odometer-ribbon-inner { 39 | display: block; 40 | -webkit-backface-visibility: hidden; 41 | } 42 | .odometer.odometer-auto-theme .odometer-digit .odometer-value, .odometer.odometer-theme-plaza .odometer-digit .odometer-value { 43 | display: block; 44 | -webkit-transform: translateZ(0); 45 | } 46 | .odometer.odometer-auto-theme .odometer-digit .odometer-value.odometer-last-value, .odometer.odometer-theme-plaza .odometer-digit .odometer-value.odometer-last-value { 47 | position: absolute; 48 | } 49 | .odometer.odometer-auto-theme.odometer-animating-up .odometer-ribbon-inner, .odometer.odometer-theme-plaza.odometer-animating-up .odometer-ribbon-inner { 50 | -webkit-transition: -webkit-transform 2s; 51 | -moz-transition: -moz-transform 2s; 52 | -ms-transition: -ms-transform 2s; 53 | -o-transition: -o-transform 2s; 54 | transition: transform 2s; 55 | } 56 | .odometer.odometer-auto-theme.odometer-animating-up.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-plaza.odometer-animating-up.odometer-animating .odometer-ribbon-inner { 57 | -webkit-transform: translateY(-100%); 58 | -moz-transform: translateY(-100%); 59 | -ms-transform: translateY(-100%); 60 | -o-transform: translateY(-100%); 61 | transform: translateY(-100%); 62 | } 63 | .odometer.odometer-auto-theme.odometer-animating-down .odometer-ribbon-inner, .odometer.odometer-theme-plaza.odometer-animating-down .odometer-ribbon-inner { 64 | -webkit-transform: translateY(-100%); 65 | -moz-transform: translateY(-100%); 66 | -ms-transform: translateY(-100%); 67 | -o-transform: translateY(-100%); 68 | transform: translateY(-100%); 69 | } 70 | .odometer.odometer-auto-theme.odometer-animating-down.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-plaza.odometer-animating-down.odometer-animating .odometer-ribbon-inner { 71 | -webkit-transition: -webkit-transform 2s; 72 | -moz-transition: -moz-transform 2s; 73 | -ms-transition: -ms-transform 2s; 74 | -o-transition: -o-transform 2s; 75 | transition: transform 2s; 76 | -webkit-transform: translateY(0); 77 | -moz-transform: translateY(0); 78 | -ms-transform: translateY(0); 79 | -o-transform: translateY(0); 80 | transform: translateY(0); 81 | } 82 | 83 | .odometer.odometer-auto-theme, .odometer.odometer-theme-plaza { 84 | -moz-border-radius: 0.15em; 85 | -webkit-border-radius: 0.15em; 86 | border-radius: 0.15em; 87 | background-color: #f0f8ff; 88 | font-family: "Helvetica Neue", sans-serif; 89 | font-weight: 100; 90 | padding: 0 0.12em; 91 | line-height: 1.2em; 92 | font-size: 1.2em; 93 | background-size: 16px 16px; 94 | } 95 | .odometer.odometer-auto-theme .odometer-digit, .odometer.odometer-theme-plaza .odometer-digit { 96 | -moz-border-radius: 0.1em; 97 | -webkit-border-radius: 0.1em; 98 | border-radius: 0.1em; 99 | padding: 0 0.03em; 100 | color: #648baf; 101 | } 102 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-inner, .odometer.odometer-theme-plaza .odometer-digit .odometer-digit-inner { 103 | left: 0.03em; 104 | } 105 | -------------------------------------------------------------------------------- /docs/welcome/landing-page.coffee: -------------------------------------------------------------------------------- 1 | THEMES = [{ 2 | name: 'minimal' 3 | numbers: [{ 4 | number: 10000 5 | description: '= 282 + 962 = 602 + 802' 6 | detail: 'two sums of two squares' 7 | source: 'http://www.wolframalpha.com/input/?i=10000' 8 | }, { 9 | number: 99999 10 | description: '= 110000110100111112' 11 | detail: '11000011010011111 in base 2' 12 | source: 'http://www.wolframalpha.com/input/?i=99999' 13 | }] 14 | }, { 15 | name: 'car' 16 | odometerOptions: 17 | format: 'd' 18 | numbers: [{ 19 | number: 13476 20 | description: 'miles driven' 21 | detail: 'by the average american each year' 22 | source: 'http://www.fhwa.dot.gov/ohim/onh00/bar8.htm' 23 | }, { 24 | number: 25114 25 | description: 'flat tires' 26 | detail: 'occur in america each hour' 27 | source: 'http://excelmathmike.blogspot.com/2011/04/phooey-on-flats-part-i.html' 28 | }] 29 | }, { 30 | name: 'digital' 31 | odometerOptions: 32 | format: 'd' 33 | numbers: [{ 34 | number: 87360 35 | description: 'minutes of tv watched' 36 | detail: 'by the average american each year' 37 | source: 'http://www.nationmaster.com/graph/med_tel_vie-media-television-viewing' 38 | }, { 39 | number: 20938 40 | description: 'minutes snoozed' 41 | detail: 'by the average american each year' 42 | source: 'http://jsfiddle.net/adamschwartz/BWgWj/show/light/' 43 | }] 44 | }, { 45 | name: 'slot-machine' 46 | numbers: [{ 47 | number: 818 48 | description: '' 49 | detail: '' 50 | source: '' 51 | }, { 52 | number: 777 53 | description: '' 54 | detail: '' 55 | source: '' 56 | }] 57 | }, { 58 | name: 'train-station' 59 | numbers: [{ 60 | number: 682 61 | description: 'train cars' 62 | detail: 'on the longest train in the world' 63 | source: 'http://en.wikipedia.org/wiki/Longest_trains' 64 | }, { 65 | number: 853 66 | description: 'people' 67 | detail: 'capacity of the largest commercial airplane' 68 | source: 'http://en.wikipedia.org/wiki/Airbus_A380' 69 | }] 70 | }] 71 | 72 | animateHeader = -> 73 | $('.title-number-section .odometer').addClass 'odometer-animating-up odometer-animating' 74 | 75 | setupOnePageScroll = -> 76 | $ -> 77 | $('.main').onepage_scroll 78 | sectionContainer: '.section' 79 | 80 | $('.down-arrow').click -> $('.main').moveDown() 81 | 82 | $(document).keydown (e) -> 83 | switch e.keyCode 84 | when 40, 34 then $('.main').moveDown() 85 | when 33, 38 then $('.main').moveUp() 86 | 87 | setupNumberSections = -> 88 | $afterSections = $('.after-number-sections') 89 | $numberSectionTemplate = $('.number-section.template') 90 | $numberSectionTemplateClone = $numberSectionTemplate.clone().removeClass('template') 91 | 92 | _.each THEMES, (theme) -> 93 | $section = $numberSectionTemplateClone.clone().addClass('number-section-theme-' + theme.name) 94 | 95 | $afterSections.before $section 96 | 97 | $odometerContainer = $section.find '.odometer-container' 98 | $odometerContainer.append('
') 99 | $odometerContainer = $odometerContainer.find('div') 100 | 101 | currentNumber = 0 102 | 103 | odometerOptions = $.extend(true, {}, theme.odometerOptions or {}, 104 | theme: theme.name 105 | value: theme.numbers[1].number 106 | el: $odometerContainer[0] 107 | ) 108 | 109 | odometer = new Odometer odometerOptions 110 | 111 | odometer.render() 112 | 113 | next = -> 114 | number = theme.numbers[currentNumber] 115 | odometer.update number.number 116 | $section.find('.number-description').html number.description 117 | $section.find('.number-detail').html number.detail 118 | $section.find('.number-source').attr 'href', number.source 119 | currentNumber = (currentNumber + 1) % theme.numbers.length 120 | 121 | next() 122 | 123 | setInterval -> 124 | next() if $section.hasClass('active') 125 | , 4 * 1000 126 | 127 | $afterSections.remove() 128 | $numberSectionTemplate.remove() 129 | 130 | init = -> 131 | setupNumberSections() 132 | setupOnePageScroll() 133 | setTimeout -> 134 | animateHeader() 135 | , 500 136 | 137 | init() 138 | -------------------------------------------------------------------------------- /themes/odometer-theme-digital.css: -------------------------------------------------------------------------------- 1 | @import url("//fonts.googleapis.com/css?family=Wallpoet"); 2 | .odometer.odometer-auto-theme, .odometer.odometer-theme-digital { 3 | display: inline-block; 4 | vertical-align: middle; 5 | *vertical-align: auto; 6 | *zoom: 1; 7 | *display: inline; 8 | position: relative; 9 | } 10 | .odometer.odometer-auto-theme .odometer-digit, .odometer.odometer-theme-digital .odometer-digit { 11 | display: inline-block; 12 | vertical-align: middle; 13 | *vertical-align: auto; 14 | *zoom: 1; 15 | *display: inline; 16 | position: relative; 17 | } 18 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-spacer, .odometer.odometer-theme-digital .odometer-digit .odometer-digit-spacer { 19 | display: inline-block; 20 | vertical-align: middle; 21 | *vertical-align: auto; 22 | *zoom: 1; 23 | *display: inline; 24 | visibility: hidden; 25 | } 26 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-inner, .odometer.odometer-theme-digital .odometer-digit .odometer-digit-inner { 27 | text-align: left; 28 | display: block; 29 | position: absolute; 30 | top: 0; 31 | left: 0; 32 | right: 0; 33 | bottom: 0; 34 | overflow: hidden; 35 | } 36 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon, .odometer.odometer-theme-digital .odometer-digit .odometer-ribbon { 37 | display: block; 38 | } 39 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon-inner, .odometer.odometer-theme-digital .odometer-digit .odometer-ribbon-inner { 40 | display: block; 41 | -webkit-backface-visibility: hidden; 42 | } 43 | .odometer.odometer-auto-theme .odometer-digit .odometer-value, .odometer.odometer-theme-digital .odometer-digit .odometer-value { 44 | display: block; 45 | -webkit-transform: translateZ(0); 46 | } 47 | .odometer.odometer-auto-theme .odometer-digit .odometer-value.odometer-last-value, .odometer.odometer-theme-digital .odometer-digit .odometer-value.odometer-last-value { 48 | position: absolute; 49 | } 50 | .odometer.odometer-auto-theme.odometer-animating-up .odometer-ribbon-inner, .odometer.odometer-theme-digital.odometer-animating-up .odometer-ribbon-inner { 51 | -webkit-transition: -webkit-transform 2s; 52 | -moz-transition: -moz-transform 2s; 53 | -ms-transition: -ms-transform 2s; 54 | -o-transition: -o-transform 2s; 55 | transition: transform 2s; 56 | } 57 | .odometer.odometer-auto-theme.odometer-animating-up.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-digital.odometer-animating-up.odometer-animating .odometer-ribbon-inner { 58 | -webkit-transform: translateY(-100%); 59 | -moz-transform: translateY(-100%); 60 | -ms-transform: translateY(-100%); 61 | -o-transform: translateY(-100%); 62 | transform: translateY(-100%); 63 | } 64 | .odometer.odometer-auto-theme.odometer-animating-down .odometer-ribbon-inner, .odometer.odometer-theme-digital.odometer-animating-down .odometer-ribbon-inner { 65 | -webkit-transform: translateY(-100%); 66 | -moz-transform: translateY(-100%); 67 | -ms-transform: translateY(-100%); 68 | -o-transform: translateY(-100%); 69 | transform: translateY(-100%); 70 | } 71 | .odometer.odometer-auto-theme.odometer-animating-down.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-digital.odometer-animating-down.odometer-animating .odometer-ribbon-inner { 72 | -webkit-transition: -webkit-transform 2s; 73 | -moz-transition: -moz-transform 2s; 74 | -ms-transition: -ms-transform 2s; 75 | -o-transition: -o-transform 2s; 76 | transition: transform 2s; 77 | -webkit-transform: translateY(0); 78 | -moz-transform: translateY(0); 79 | -ms-transform: translateY(0); 80 | -o-transform: translateY(0); 81 | transform: translateY(0); 82 | } 83 | 84 | .odometer.odometer-auto-theme, .odometer.odometer-theme-digital { 85 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHJhZGlhbEdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY3g9IjUwJSIgY3k9IjUwJSIgcj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzhiZjVhNSIgc3RvcC1vcGFjaXR5PSIwLjQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMwMDAwMDAiLz48L3JhZGlhbEdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPjwvc3ZnPiA='); 86 | background-size: 100%; 87 | background-image: -moz-radial-gradient(rgba(139, 245, 165, 0.4), #000000); 88 | background-image: -webkit-radial-gradient(rgba(139, 245, 165, 0.4), #000000); 89 | background-image: radial-gradient(rgba(139, 245, 165, 0.4), #000000); 90 | background-color: #000; 91 | font-family: "Wallpoet", monospace; 92 | padding: 0 0.2em; 93 | line-height: 1.1em; 94 | color: #8bf5a5; 95 | } 96 | .odometer.odometer-auto-theme .odometer-digit + .odometer-digit, .odometer.odometer-theme-digital .odometer-digit + .odometer-digit { 97 | margin-left: 0.1em; 98 | } 99 | -------------------------------------------------------------------------------- /docs/welcome/landing-page.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var THEMES, animateHeader, init, setupNumberSections, setupOnePageScroll; 3 | 4 | THEMES = [ 5 | { 6 | name: 'minimal', 7 | numbers: [ 8 | { 9 | number: 10000, 10 | description: '= 282 + 962 = 602 + 802', 11 | detail: 'two sums of two squares', 12 | source: 'http://www.wolframalpha.com/input/?i=10000' 13 | }, { 14 | number: 99999, 15 | description: '= 110000110100111112', 16 | detail: '11000011010011111 in base 2', 17 | source: 'http://www.wolframalpha.com/input/?i=99999' 18 | } 19 | ] 20 | }, { 21 | name: 'car', 22 | odometerOptions: { 23 | format: 'd' 24 | }, 25 | numbers: [ 26 | { 27 | number: 13476, 28 | description: 'miles driven', 29 | detail: 'by the average american each year', 30 | source: 'http://www.fhwa.dot.gov/ohim/onh00/bar8.htm' 31 | }, { 32 | number: 25114, 33 | description: 'flat tires', 34 | detail: 'occur in america each hour', 35 | source: 'http://excelmathmike.blogspot.com/2011/04/phooey-on-flats-part-i.html' 36 | } 37 | ] 38 | }, { 39 | name: 'digital', 40 | odometerOptions: { 41 | format: 'd' 42 | }, 43 | numbers: [ 44 | { 45 | number: 87360, 46 | description: 'minutes of tv watched', 47 | detail: 'by the average american each year', 48 | source: 'http://www.nationmaster.com/graph/med_tel_vie-media-television-viewing' 49 | }, { 50 | number: 20938, 51 | description: 'minutes snoozed', 52 | detail: 'by the average american each year', 53 | source: 'http://jsfiddle.net/adamschwartz/BWgWj/show/light/' 54 | } 55 | ] 56 | }, { 57 | name: 'slot-machine', 58 | numbers: [ 59 | { 60 | number: 818, 61 | description: '', 62 | detail: '', 63 | source: '' 64 | }, { 65 | number: 777, 66 | description: '', 67 | detail: '', 68 | source: '' 69 | } 70 | ] 71 | }, { 72 | name: 'train-station', 73 | numbers: [ 74 | { 75 | number: 682, 76 | description: 'train cars', 77 | detail: 'on the longest train in the world', 78 | source: 'http://en.wikipedia.org/wiki/Longest_trains' 79 | }, { 80 | number: 853, 81 | description: 'people', 82 | detail: 'capacity of the largest commercial airplane', 83 | source: 'http://en.wikipedia.org/wiki/Airbus_A380' 84 | } 85 | ] 86 | } 87 | ]; 88 | 89 | animateHeader = function() { 90 | return $('.title-number-section .odometer').addClass('odometer-animating-up odometer-animating'); 91 | }; 92 | 93 | setupOnePageScroll = function() { 94 | return $(function() { 95 | $('.main').onepage_scroll({ 96 | sectionContainer: '.section' 97 | }); 98 | $('.down-arrow').click(function() { 99 | return $('.main').moveDown(); 100 | }); 101 | return $(document).keydown(function(e) { 102 | switch (e.keyCode) { 103 | case 40: 104 | case 34: 105 | return $('.main').moveDown(); 106 | case 33: 107 | case 38: 108 | return $('.main').moveUp(); 109 | } 110 | }); 111 | }); 112 | }; 113 | 114 | setupNumberSections = function() { 115 | var $afterSections, $numberSectionTemplate, $numberSectionTemplateClone; 116 | $afterSections = $('.after-number-sections'); 117 | $numberSectionTemplate = $('.number-section.template'); 118 | $numberSectionTemplateClone = $numberSectionTemplate.clone().removeClass('template'); 119 | _.each(THEMES, function(theme) { 120 | var $odometerContainer, $section, currentNumber, next, odometer, odometerOptions; 121 | $section = $numberSectionTemplateClone.clone().addClass('number-section-theme-' + theme.name); 122 | $afterSections.before($section); 123 | $odometerContainer = $section.find('.odometer-container'); 124 | $odometerContainer.append('
'); 125 | $odometerContainer = $odometerContainer.find('div'); 126 | currentNumber = 0; 127 | odometerOptions = $.extend(true, {}, theme.odometerOptions || {}, { 128 | theme: theme.name, 129 | value: theme.numbers[1].number, 130 | el: $odometerContainer[0] 131 | }); 132 | odometer = new Odometer(odometerOptions); 133 | odometer.render(); 134 | next = function() { 135 | var number; 136 | number = theme.numbers[currentNumber]; 137 | odometer.update(number.number); 138 | $section.find('.number-description').html(number.description); 139 | $section.find('.number-detail').html(number.detail); 140 | $section.find('.number-source').attr('href', number.source); 141 | return currentNumber = (currentNumber + 1) % theme.numbers.length; 142 | }; 143 | next(); 144 | return setInterval(function() { 145 | if ($section.hasClass('active')) { 146 | return next(); 147 | } 148 | }, 4 * 1000); 149 | }); 150 | $afterSections.remove(); 151 | return $numberSectionTemplate.remove(); 152 | }; 153 | 154 | init = function() { 155 | setupNumberSections(); 156 | setupOnePageScroll(); 157 | return setTimeout(function() { 158 | return animateHeader(); 159 | }, 500); 160 | }; 161 | 162 | init(); 163 | 164 | }).call(this); 165 | -------------------------------------------------------------------------------- /themes/odometer-theme-train-station.css: -------------------------------------------------------------------------------- 1 | @import url("//fonts.googleapis.com/css?family=Economica"); 2 | .odometer.odometer-auto-theme, .odometer.odometer-theme-train-station { 3 | display: inline-block; 4 | vertical-align: middle; 5 | *vertical-align: auto; 6 | *zoom: 1; 7 | *display: inline; 8 | position: relative; 9 | } 10 | .odometer.odometer-auto-theme .odometer-digit, .odometer.odometer-theme-train-station .odometer-digit { 11 | display: inline-block; 12 | vertical-align: middle; 13 | *vertical-align: auto; 14 | *zoom: 1; 15 | *display: inline; 16 | position: relative; 17 | } 18 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-spacer, .odometer.odometer-theme-train-station .odometer-digit .odometer-digit-spacer { 19 | display: inline-block; 20 | vertical-align: middle; 21 | *vertical-align: auto; 22 | *zoom: 1; 23 | *display: inline; 24 | visibility: hidden; 25 | } 26 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-inner, .odometer.odometer-theme-train-station .odometer-digit .odometer-digit-inner { 27 | text-align: left; 28 | display: block; 29 | position: absolute; 30 | top: 0; 31 | left: 0; 32 | right: 0; 33 | bottom: 0; 34 | overflow: hidden; 35 | } 36 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon, .odometer.odometer-theme-train-station .odometer-digit .odometer-ribbon { 37 | display: block; 38 | } 39 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon-inner, .odometer.odometer-theme-train-station .odometer-digit .odometer-ribbon-inner { 40 | display: block; 41 | -webkit-backface-visibility: hidden; 42 | } 43 | .odometer.odometer-auto-theme .odometer-digit .odometer-value, .odometer.odometer-theme-train-station .odometer-digit .odometer-value { 44 | display: block; 45 | -webkit-transform: translateZ(0); 46 | } 47 | .odometer.odometer-auto-theme .odometer-digit .odometer-value.odometer-last-value, .odometer.odometer-theme-train-station .odometer-digit .odometer-value.odometer-last-value { 48 | position: absolute; 49 | } 50 | .odometer.odometer-auto-theme.odometer-animating-up .odometer-ribbon-inner, .odometer.odometer-theme-train-station.odometer-animating-up .odometer-ribbon-inner { 51 | -webkit-transition: -webkit-transform 2s; 52 | -moz-transition: -moz-transform 2s; 53 | -ms-transition: -ms-transform 2s; 54 | -o-transition: -o-transform 2s; 55 | transition: transform 2s; 56 | } 57 | .odometer.odometer-auto-theme.odometer-animating-up.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-train-station.odometer-animating-up.odometer-animating .odometer-ribbon-inner { 58 | -webkit-transform: translateY(-100%); 59 | -moz-transform: translateY(-100%); 60 | -ms-transform: translateY(-100%); 61 | -o-transform: translateY(-100%); 62 | transform: translateY(-100%); 63 | } 64 | .odometer.odometer-auto-theme.odometer-animating-down .odometer-ribbon-inner, .odometer.odometer-theme-train-station.odometer-animating-down .odometer-ribbon-inner { 65 | -webkit-transform: translateY(-100%); 66 | -moz-transform: translateY(-100%); 67 | -ms-transform: translateY(-100%); 68 | -o-transform: translateY(-100%); 69 | transform: translateY(-100%); 70 | } 71 | .odometer.odometer-auto-theme.odometer-animating-down.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-train-station.odometer-animating-down.odometer-animating .odometer-ribbon-inner { 72 | -webkit-transition: -webkit-transform 2s; 73 | -moz-transition: -moz-transform 2s; 74 | -ms-transition: -ms-transform 2s; 75 | -o-transition: -o-transform 2s; 76 | transition: transform 2s; 77 | -webkit-transform: translateY(0); 78 | -moz-transform: translateY(0); 79 | -ms-transform: translateY(0); 80 | -o-transform: translateY(0); 81 | transform: translateY(0); 82 | } 83 | 84 | .odometer.odometer-auto-theme, .odometer.odometer-theme-train-station { 85 | font-family: "Economica", sans-serif; 86 | } 87 | .odometer.odometer-auto-theme .odometer-digit, .odometer.odometer-theme-train-station .odometer-digit { 88 | display: inline-block; 89 | vertical-align: middle; 90 | *vertical-align: auto; 91 | *zoom: 1; 92 | *display: inline; 93 | -moz-border-radius: 0.1em; 94 | -webkit-border-radius: 0.1em; 95 | border-radius: 0.1em; 96 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzExMTExMSIvPjxzdG9wIG9mZnNldD0iMzUlIiBzdG9wLWNvbG9yPSIjMTExMTExIi8+PHN0b3Agb2Zmc2V0PSI1NSUiIHN0b3AtY29sb3I9IiMzMzMzMzMiLz48c3RvcCBvZmZzZXQ9IjU1JSIgc3RvcC1jb2xvcj0iIzExMTExMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzExMTExMSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); 97 | background-size: 100%; 98 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #111111), color-stop(35%, #111111), color-stop(55%, #333333), color-stop(55%, #111111), color-stop(100%, #111111)); 99 | background-image: -moz-linear-gradient(top, #111111 0%, #111111 35%, #333333 55%, #111111 55%, #111111 100%); 100 | background-image: -webkit-linear-gradient(top, #111111 0%, #111111 35%, #333333 55%, #111111 55%, #111111 100%); 101 | background-image: linear-gradient(to bottom, #111111 0%, #111111 35%, #333333 55%, #111111 55%, #111111 100%); 102 | background-color: #222; 103 | padding: 0 0.15em; 104 | color: #fff; 105 | } 106 | .odometer.odometer-auto-theme .odometer-digit + .odometer-digit, .odometer.odometer-theme-train-station .odometer-digit + .odometer-digit { 107 | margin-left: 0.1em; 108 | } 109 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-inner, .odometer.odometer-theme-train-station .odometer-digit .odometer-digit-inner { 110 | left: 0.15em; 111 | } 112 | -------------------------------------------------------------------------------- /docs/intro.md: -------------------------------------------------------------------------------- 1 | Odometer 2 | ======== 3 | 4 | 36 | 37 | 38 | 41 | 42 | 43 | 80 | 81 |

GitHub ★ s so far:
0

82 |
Star odometer on GitHub to to see it update.
83 | 84 | Odometer is a Javascript and CSS library for smoothly transitioning numbers. 85 | See the [demo page](http://github.hubspot.com/odometer/docs/welcome) for some examples. 86 | 87 | Odometer's animations are handled entirely in CSS using transforms making 88 | them extremely performant, with automatic fallback on older browsers. 89 | 90 | The library and largest theme is less than 3kb when minified and compressed. 91 | 92 | All of [the themes](http://github.hubspot.com/odometer/api/themes/) can be resized by setting the `font-size` of the `.odometer` element. 93 | 94 | Usage 95 | ----- 96 | 97 | **The simplest possible usage is just including [the javascript](https://raw.github.com/HubSpot/odometer/v0.4.8/odometer.min.js) and a [theme css](http://github.hubspot.com/odometer/api/themes/) 98 | file on your page. Add the `odometer` class to any numbers you'd like to animate on change. You're done.** 99 | 100 | Just set the `innerHTML`, `innerText`, or use jQuery's `.text()` or `.html()` methods to change their contents, and the animation 101 | will happen automatically. Any libraries you're using to update their value, provided they don't update by erasing and rerendering 102 | the `odometer` element, will work just fine. 103 | 104 | On older browsers, it will automatically fallback to a simpler animation which won't tax their fragile javascript runtime. 105 | 106 | Example 107 | ------- 108 | 109 |
123
110 | 111 | Play with this simple example on [jsFiddle](http://jsfiddle.net/adamschwartz/rx6BQ/). 112 | 113 | Advanced 114 | -------- 115 | 116 | You can set options by creating a `odometerOptions` object: 117 | 118 | ```javascript 119 | window.odometerOptions = { 120 | auto: false, // Don't automatically initialize everything with class 'odometer' 121 | selector: '.my-numbers', // Change the selector used to automatically find things to be animated 122 | format: '(,ddd).dd', // Change how digit groups are formatted, and how many digits are shown after the decimal point 123 | duration: 3000, // Change how long the javascript expects the CSS animation to take 124 | theme: 'car', // Specify the theme (if you have more than one theme css file on the page) 125 | animation: 'count' // Count is a simpler animation method which just increments the value, 126 | // use it when you're looking for something more subtle. 127 | }; 128 | ``` 129 | 130 | You can manually initialize an odometer with the global `Odometer`: 131 | 132 | ```javascript 133 | var el = document.querySelector('.some-element'); 134 | 135 | od = new Odometer({ 136 | el: el, 137 | value: 333555, 138 | 139 | // Any option (other than auto and selector) can be passed in here 140 | format: '', 141 | theme: 'digital' 142 | }); 143 | 144 | od.update(555) 145 | // or 146 | el.innerHTML = 555 147 | ``` 148 | 149 | Format 150 | ------ 151 | 152 | The format option allows you to configure how the digit groups are formatted, 153 | and how many digits are shown after the decimal point. 154 | 155 | Format - Example 156 | (,ddd) - 12,345,678 157 | (,ddd).dd - 12,345,678.09 158 | (.ddd),dd - 12.345.678,09 159 | ( ddd),dd - 12 345 678,09 160 | d - 12345678 161 | 162 | Browser Support 163 | --------------- 164 | 165 | Odometer is intended to support IE8+, FF4+, Safari 6+, and Chrome. 166 | 167 | Dependencies 168 | ------------ 169 | 170 | None! 171 | 172 | Contributing 173 | ------------ 174 | 175 | Odometer is built using Grunt. To setup the build environment you first 176 | must have Node.js installed. Then: 177 | 178 | ```bash 179 | # In the project directory 180 | npm install 181 | ``` 182 | 183 | To build the project: 184 | ```bash 185 | grunt 186 | ``` 187 | 188 | To have it watch for changes and build automatically: 189 | ```bash 190 | grunt watch 191 | ``` 192 | 193 | We welcome pull requests! 194 | -------------------------------------------------------------------------------- /themes/odometer-theme-car.css: -------------------------------------------------------------------------------- 1 | @import url("//fonts.googleapis.com/css?family=Arimo"); 2 | .odometer.odometer-auto-theme, .odometer.odometer-theme-car { 3 | display: inline-block; 4 | vertical-align: middle; 5 | *vertical-align: auto; 6 | *zoom: 1; 7 | *display: inline; 8 | position: relative; 9 | } 10 | .odometer.odometer-auto-theme .odometer-digit, .odometer.odometer-theme-car .odometer-digit { 11 | display: inline-block; 12 | vertical-align: middle; 13 | *vertical-align: auto; 14 | *zoom: 1; 15 | *display: inline; 16 | position: relative; 17 | } 18 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-spacer, .odometer.odometer-theme-car .odometer-digit .odometer-digit-spacer { 19 | display: inline-block; 20 | vertical-align: middle; 21 | *vertical-align: auto; 22 | *zoom: 1; 23 | *display: inline; 24 | visibility: hidden; 25 | } 26 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-inner, .odometer.odometer-theme-car .odometer-digit .odometer-digit-inner { 27 | text-align: left; 28 | display: block; 29 | position: absolute; 30 | top: 0; 31 | left: 0; 32 | right: 0; 33 | bottom: 0; 34 | overflow: hidden; 35 | } 36 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon, .odometer.odometer-theme-car .odometer-digit .odometer-ribbon { 37 | display: block; 38 | } 39 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon-inner, .odometer.odometer-theme-car .odometer-digit .odometer-ribbon-inner { 40 | display: block; 41 | -webkit-backface-visibility: hidden; 42 | } 43 | .odometer.odometer-auto-theme .odometer-digit .odometer-value, .odometer.odometer-theme-car .odometer-digit .odometer-value { 44 | display: block; 45 | -webkit-transform: translateZ(0); 46 | } 47 | .odometer.odometer-auto-theme .odometer-digit .odometer-value.odometer-last-value, .odometer.odometer-theme-car .odometer-digit .odometer-value.odometer-last-value { 48 | position: absolute; 49 | } 50 | .odometer.odometer-auto-theme.odometer-animating-up .odometer-ribbon-inner, .odometer.odometer-theme-car.odometer-animating-up .odometer-ribbon-inner { 51 | -webkit-transition: -webkit-transform 2s; 52 | -moz-transition: -moz-transform 2s; 53 | -ms-transition: -ms-transform 2s; 54 | -o-transition: -o-transform 2s; 55 | transition: transform 2s; 56 | } 57 | .odometer.odometer-auto-theme.odometer-animating-up.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-car.odometer-animating-up.odometer-animating .odometer-ribbon-inner { 58 | -webkit-transform: translateY(-100%); 59 | -moz-transform: translateY(-100%); 60 | -ms-transform: translateY(-100%); 61 | -o-transform: translateY(-100%); 62 | transform: translateY(-100%); 63 | } 64 | .odometer.odometer-auto-theme.odometer-animating-down .odometer-ribbon-inner, .odometer.odometer-theme-car.odometer-animating-down .odometer-ribbon-inner { 65 | -webkit-transform: translateY(-100%); 66 | -moz-transform: translateY(-100%); 67 | -ms-transform: translateY(-100%); 68 | -o-transform: translateY(-100%); 69 | transform: translateY(-100%); 70 | } 71 | .odometer.odometer-auto-theme.odometer-animating-down.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-car.odometer-animating-down.odometer-animating .odometer-ribbon-inner { 72 | -webkit-transition: -webkit-transform 2s; 73 | -moz-transition: -moz-transform 2s; 74 | -ms-transition: -ms-transform 2s; 75 | -o-transition: -o-transform 2s; 76 | transition: transform 2s; 77 | -webkit-transform: translateY(0); 78 | -moz-transform: translateY(0); 79 | -ms-transform: translateY(0); 80 | -o-transform: translateY(0); 81 | transform: translateY(0); 82 | } 83 | 84 | .odometer.odometer-auto-theme, .odometer.odometer-theme-car { 85 | -moz-border-radius: 0.34em; 86 | -webkit-border-radius: 0.34em; 87 | border-radius: 0.34em; 88 | font-family: "Arimo", monospace; 89 | padding: 0.15em; 90 | background: #000; 91 | color: #eee0d3; 92 | } 93 | .odometer.odometer-auto-theme .odometer-digit, .odometer.odometer-theme-car .odometer-digit { 94 | -moz-box-shadow: inset 0 0 0.3em rgba(0, 0, 0, 0.8); 95 | -webkit-box-shadow: inset 0 0 0.3em rgba(0, 0, 0, 0.8); 96 | box-shadow: inset 0 0 0.3em rgba(0, 0, 0, 0.8); 97 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzMzMzMzMyIvPjxzdG9wIG9mZnNldD0iNDAlIiBzdG9wLWNvbG9yPSIjMzMzMzMzIi8+PHN0b3Agb2Zmc2V0PSI2MCUiIHN0b3AtY29sb3I9IiMxMDEwMTAiLz48c3RvcCBvZmZzZXQ9IjgwJSIgc3RvcC1jb2xvcj0iIzMzMzMzMyIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzMzMzMzMyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); 98 | background-size: 100%; 99 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #333333), color-stop(40%, #333333), color-stop(60%, #101010), color-stop(80%, #333333), color-stop(100%, #333333)); 100 | background-image: -moz-linear-gradient(top, #333333 0%, #333333 40%, #101010 60%, #333333 80%, #333333 100%); 101 | background-image: -webkit-linear-gradient(top, #333333 0%, #333333 40%, #101010 60%, #333333 80%, #333333 100%); 102 | background-image: linear-gradient(to bottom, #333333 0%, #333333 40%, #101010 60%, #333333 80%, #333333 100%); 103 | padding: 0 0.15em; 104 | } 105 | .odometer.odometer-auto-theme .odometer-digit:first-child, .odometer.odometer-theme-car .odometer-digit:first-child { 106 | -moz-border-radius: 0.2em 0 0 0.2em; 107 | -webkit-border-radius: 0.2em; 108 | border-radius: 0.2em 0 0 0.2em; 109 | } 110 | .odometer.odometer-auto-theme .odometer-digit:last-child, .odometer.odometer-theme-car .odometer-digit:last-child { 111 | -moz-border-radius: 0 0.2em 0.2em 0; 112 | -webkit-border-radius: 0; 113 | border-radius: 0 0.2em 0.2em 0; 114 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2VlZTBkMyIvPjxzdG9wIG9mZnNldD0iNDAlIiBzdG9wLWNvbG9yPSIjZWVlMGQzIi8+PHN0b3Agb2Zmc2V0PSI2MCUiIHN0b3AtY29sb3I9IiNiYmFhOWEiLz48c3RvcCBvZmZzZXQ9IjgwJSIgc3RvcC1jb2xvcj0iI2VlZTBkMyIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2VlZTBkMyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); 115 | background-size: 100%; 116 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #eee0d3), color-stop(40%, #eee0d3), color-stop(60%, #bbaa9a), color-stop(80%, #eee0d3), color-stop(100%, #eee0d3)); 117 | background-image: -moz-linear-gradient(top, #eee0d3 0%, #eee0d3 40%, #bbaa9a 60%, #eee0d3 80%, #eee0d3 100%); 118 | background-image: -webkit-linear-gradient(top, #eee0d3 0%, #eee0d3 40%, #bbaa9a 60%, #eee0d3 80%, #eee0d3 100%); 119 | background-image: linear-gradient(to bottom, #eee0d3 0%, #eee0d3 40%, #bbaa9a 60%, #eee0d3 80%, #eee0d3 100%); 120 | background-color: #eee0d3; 121 | color: #000; 122 | } 123 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-inner, .odometer.odometer-theme-car .odometer-digit .odometer-digit-inner { 124 | left: 0.15em; 125 | } 126 | .odometer.odometer-auto-theme.odometer-animating-up .odometer-ribbon-inner, .odometer.odometer-auto-theme.odometer-animating-down.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-car.odometer-animating-up .odometer-ribbon-inner, .odometer.odometer-theme-car.odometer-animating-down.odometer-animating .odometer-ribbon-inner { 127 | -webkit-transition-timing-function: linear; 128 | -moz-transition-timing-function: linear; 129 | -ms-transition-timing-function: linear; 130 | -o-transition-timing-function: linear; 131 | transition-timing-function: linear; 132 | } 133 | -------------------------------------------------------------------------------- /docs/welcome/landing-page.css: -------------------------------------------------------------------------------- 1 | /* Prism.js */ 2 | code[class*="language-"], pre[class*="language-"] {color: black; text-shadow: 0 1px white; font-family: Consolas, Monaco, 'Andale Mono', monospace; direction: ltr; text-align: left; white-space: pre; word-spacing: normal; -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; -webkit-hyphens: none; -moz-hyphens: none; -ms-hyphens: none; hyphens: none; } pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {text-shadow: none; background: #b3d4fc; } pre[class*="language-"]::selection, pre[class*="language-"] ::selection, code[class*="language-"]::selection, code[class*="language-"] ::selection {text-shadow: none; background: #b3d4fc; } @media print {code[class*="language-"], pre[class*="language-"] {text-shadow: none; } } /* Code blocks */ pre[class*="language-"] {padding: 1em; margin: .5em 0; overflow: auto; font-size: .7em; } :not(pre) > code[class*="language-"], pre[class*="language-"] {background: #f5f2f0; } /* Inline code */ :not(pre) > code[class*="language-"] {padding: .1em; border-radius: .3em; } .token.comment, .token.prolog, .token.doctype, .token.cdata {color: slategray; } .token.punctuation {color: #999; } .namespace {opacity: .7; } .token.property, .token.tag, .token.boolean, .token.number, .token.constant, .token.symbol {color: #905; } .token.selector, .token.attr-name, .token.string, .token.builtin {color: #690; } .token.operator, .token.entity, .token.url, .language-css .token.string, .style .token.string, .token.variable {color: #a67f59; background: hsla(0,0%,100%,.5); } .token.atrule, .token.attr-value, .token.keyword {color: #07a; } .token.regex, .token.important {color: #e90; } .token.important {font-weight: bold; } .token.entity {cursor: help; } 3 | 4 | html, body { 5 | height: 100%; 6 | margin: 0; 7 | } 8 | 9 | html { font-size: 150%; } 10 | @media (max-width: 1200px) { html { font-size: 125%; } } 11 | @media (max-width: 1000px) { html { font-size: 100%; } } 12 | @media (max-width: 767px) { html { font-size: 80%; } } 13 | @media (max-width: 568px) { html { font-size: 50%; } } 14 | @media (max-width: 480px) { html { font-size: 35%; } } 15 | @media (max-width: 320px) { html { font-size: 25%; } } 16 | 17 | body { 18 | font-family: "proxima-nova", "Helvetica Neue", sans-serif; 19 | } 20 | 21 | .template { 22 | display: none; 23 | } 24 | 25 | .section { 26 | height: 100%; 27 | width: 100%; 28 | position: relative; 29 | -webkit-transform: translateZ(0); 30 | } 31 | 32 | .number-container, .about-section { 33 | position: absolute; 34 | margin: auto; 35 | top: 0; 36 | left: 0; 37 | right: 0; 38 | bottom: 0; 39 | width: 100%; 40 | height: 20em; 41 | text-align: center; 42 | -webkit-transform: translateZ(0); 43 | } 44 | 45 | .about-section { 46 | font-size: 1em; 47 | text-align: left; 48 | width: 30em; 49 | height: 22em; 50 | max-width: 80%; 51 | } 52 | 53 | .about-section h1 { 54 | margin-top: 0; 55 | text-align: center; 56 | } 57 | 58 | .about-section a { 59 | color: inherit; 60 | } 61 | 62 | .number-container .odometer-container { 63 | font-size: 6em; 64 | } 65 | 66 | .number-description { 67 | display: inline-block; 68 | text-transform: uppercase; 69 | font-size: 2.5em; 70 | letter-spacing: .08em; 71 | margin: 1em 0 0; 72 | font-weight: bold; 73 | } 74 | 75 | .number-detail { 76 | display: inline-block; 77 | text-transform: uppercase; 78 | font-size: 1.5em; 79 | letter-spacing: .08em; 80 | opacity: 0.5; 81 | } 82 | 83 | .title-number-section .number-container .odometer-container { 84 | font-size: 4em; 85 | } 86 | 87 | .title-number-section .number-description { 88 | font-size: 1.55em; 89 | margin: .5em 0 .25em; 90 | color: #666; 91 | } 92 | 93 | @media (max-width: 767px) { 94 | .title-number-section .number-container .odometer-container { 95 | font-size: 5em; 96 | } 97 | 98 | .title-number-section .number-description { 99 | font-size: 1.8em 100 | } 101 | } 102 | 103 | @media (max-width: 568px) { 104 | .title-number-section .number-container .odometer-container { 105 | font-size: 6em; 106 | } 107 | 108 | .title-number-section .number-description { 109 | font-size: 2.2em; 110 | } 111 | } 112 | 113 | .button { 114 | text-decoration: none; 115 | color: #000; 116 | font-size: 0.7em; 117 | padding: .3em .7em .4em; 118 | cursor: pointer; 119 | margin: 2em auto; 120 | border: .15em solid; 121 | display: inline-block; 122 | line-height: 1.7; 123 | } 124 | 125 | .dark-button { 126 | color: white; 127 | background-color: #222; 128 | } 129 | 130 | .number-source { 131 | text-decoration: none; 132 | color: inherit; 133 | } 134 | 135 | .number-source:hover .number-detail { 136 | border-bottom: 1px dotted; 137 | opacity: 0.8; 138 | } 139 | 140 | .number-section.number-section-theme-minimal { 141 | background: #eee; 142 | color: #444; 143 | } 144 | 145 | .number-section.number-section-theme-minimal .odometer.odometer-theme-minimal .odometer-digit .odometer-digit-inner { 146 | text-align: right; 147 | } 148 | 149 | .number-section.number-section-theme-digital .odometer { 150 | border: .1em solid rgba(139, 245, 165, 0.4); 151 | } 152 | 153 | .number-section.number-section-theme-digital { 154 | background: #000; 155 | color: #8bf5a5; 156 | } 157 | 158 | .number-section.number-section-theme-car { 159 | background: #eee0d3; 160 | color: #333; 161 | } 162 | 163 | .number-section.number-section-theme-train-station { 164 | background: #ccc; 165 | color: #000; 166 | } 167 | 168 | .number-section.number-section-theme-slot-machine { 169 | background-image: -webkit-gradient(linear, 0% 50%, 100% 50%, color-stop(0%, #000000), color-stop(2%, #000000), color-stop(2%, #ffa500), color-stop(50%, #ffe000), color-stop(98%, #ffa500), color-stop(98%, #000000), color-stop(100%, #000000)); 170 | background-image: -webkit-linear-gradient(left, #000000 0%, #000000 2%, #ffa500 2%, #ffe000 50%, #ffa500 98%, #000000 98%, #000000 100%); 171 | background-image: -moz-linear-gradient(left, #000000 0%, #000000 2%, #ffa500 2%, #ffe000 50%, #ffa500 98%, #000000 98%, #000000 100%); 172 | background-image: -o-linear-gradient(left, #000000 0%, #000000 2%, #ffa500 2%, #ffe000 50%, #ffa500 98%, #000000 98%, #000000 100%); 173 | background-image: -ms-linear-gradient(left, #000000 0%, #000000 2%, #ffa500 2%, #ffe000 50%, #ffa500 98%, #000000 98%, #000000 100%); 174 | background-image: linear-gradient(left, #000000 0%, #000000 2%, #ffa500 2%, #ffe000 50%, #ffa500 98%, #000000 98%, #000000 100%); 175 | background-size: 31em 100%; 176 | background-repeat: no-repeat; 177 | background-position: center; 178 | color: #000; 179 | } 180 | 181 | .odometer.odometer-theme-odometer { 182 | line-height: 2em; 183 | } 184 | 185 | .odometer-theme-odometer:before { 186 | content: " "; 187 | position: absolute; 188 | left: 0; 189 | top: 0; 190 | width: 100%; 191 | height: .1em; 192 | background: #000; 193 | } 194 | 195 | .odometer-theme-odometer:after { 196 | content: " "; 197 | position: absolute; 198 | left: 0; 199 | bottom: 0; 200 | width: 100%; 201 | height: .1em; 202 | background: #000; 203 | } 204 | 205 | .odometer-theme-odometer .odometer-value { 206 | width: 100%; 207 | text-align: center; 208 | } 209 | 210 | .down-arrow { 211 | position: absolute; 212 | cursor: pointer; 213 | margin: auto; 214 | display: block; 215 | left: 0; 216 | right: 0; 217 | font-size: 2em; 218 | bottom: 1em; 219 | height: 1em; 220 | width: 1em; 221 | text-align: center; 222 | opacity: 0.5; 223 | -webkit-transform: translateZ(0); 224 | } 225 | 226 | .down-arrow:hover { 227 | opacity: 1; 228 | } 229 | 230 | .down-arrow:before { 231 | content: "\2193"; 232 | display: block; 233 | } 234 | 235 | /* Social sharing */ 236 | 237 | #retweet_button { 238 | position: fixed; 239 | bottom: 30px; 240 | left: 30px; 241 | width: 100px; 242 | z-index: 3; 243 | } 244 | 245 | #github_stars { 246 | position: fixed; 247 | bottom: 30px; 248 | left: 130px; 249 | width: 100px; 250 | z-index: 3; 251 | } 252 | 253 | .social-sharing-wrapper { -webkit-filter: grayscale(1) contrast(1.3); } 254 | .social-sharing-wrapper:hover { -webkit-filter: none; } 255 | -------------------------------------------------------------------------------- /themes/odometer-theme-slot-machine.css: -------------------------------------------------------------------------------- 1 | @import url("//fonts.googleapis.com/css?family=Rye"); 2 | .odometer.odometer-auto-theme, .odometer.odometer-theme-slot-machine { 3 | display: inline-block; 4 | vertical-align: middle; 5 | *vertical-align: auto; 6 | *zoom: 1; 7 | *display: inline; 8 | position: relative; 9 | } 10 | .odometer.odometer-auto-theme .odometer-digit, .odometer.odometer-theme-slot-machine .odometer-digit { 11 | display: inline-block; 12 | vertical-align: middle; 13 | *vertical-align: auto; 14 | *zoom: 1; 15 | *display: inline; 16 | position: relative; 17 | } 18 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-spacer, .odometer.odometer-theme-slot-machine .odometer-digit .odometer-digit-spacer { 19 | display: inline-block; 20 | vertical-align: middle; 21 | *vertical-align: auto; 22 | *zoom: 1; 23 | *display: inline; 24 | visibility: hidden; 25 | } 26 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-inner, .odometer.odometer-theme-slot-machine .odometer-digit .odometer-digit-inner { 27 | text-align: left; 28 | display: block; 29 | position: absolute; 30 | top: 0; 31 | left: 0; 32 | right: 0; 33 | bottom: 0; 34 | overflow: hidden; 35 | } 36 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon, .odometer.odometer-theme-slot-machine .odometer-digit .odometer-ribbon { 37 | display: block; 38 | } 39 | .odometer.odometer-auto-theme .odometer-digit .odometer-ribbon-inner, .odometer.odometer-theme-slot-machine .odometer-digit .odometer-ribbon-inner { 40 | display: block; 41 | -webkit-backface-visibility: hidden; 42 | } 43 | .odometer.odometer-auto-theme .odometer-digit .odometer-value, .odometer.odometer-theme-slot-machine .odometer-digit .odometer-value { 44 | display: block; 45 | -webkit-transform: translateZ(0); 46 | } 47 | .odometer.odometer-auto-theme .odometer-digit .odometer-value.odometer-last-value, .odometer.odometer-theme-slot-machine .odometer-digit .odometer-value.odometer-last-value { 48 | position: absolute; 49 | } 50 | .odometer.odometer-auto-theme.odometer-animating-up .odometer-ribbon-inner, .odometer.odometer-theme-slot-machine.odometer-animating-up .odometer-ribbon-inner { 51 | -webkit-transition: -webkit-transform 2s; 52 | -moz-transition: -moz-transform 2s; 53 | -ms-transition: -ms-transform 2s; 54 | -o-transition: -o-transform 2s; 55 | transition: transform 2s; 56 | } 57 | .odometer.odometer-auto-theme.odometer-animating-up.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-slot-machine.odometer-animating-up.odometer-animating .odometer-ribbon-inner { 58 | -webkit-transform: translateY(-100%); 59 | -moz-transform: translateY(-100%); 60 | -ms-transform: translateY(-100%); 61 | -o-transform: translateY(-100%); 62 | transform: translateY(-100%); 63 | } 64 | .odometer.odometer-auto-theme.odometer-animating-down .odometer-ribbon-inner, .odometer.odometer-theme-slot-machine.odometer-animating-down .odometer-ribbon-inner { 65 | -webkit-transform: translateY(-100%); 66 | -moz-transform: translateY(-100%); 67 | -ms-transform: translateY(-100%); 68 | -o-transform: translateY(-100%); 69 | transform: translateY(-100%); 70 | } 71 | .odometer.odometer-auto-theme.odometer-animating-down.odometer-animating .odometer-ribbon-inner, .odometer.odometer-theme-slot-machine.odometer-animating-down.odometer-animating .odometer-ribbon-inner { 72 | -webkit-transition: -webkit-transform 2s; 73 | -moz-transition: -moz-transform 2s; 74 | -ms-transition: -ms-transform 2s; 75 | -o-transition: -o-transform 2s; 76 | transition: transform 2s; 77 | -webkit-transform: translateY(0); 78 | -moz-transform: translateY(0); 79 | -ms-transform: translateY(0); 80 | -o-transform: translateY(0); 81 | transform: translateY(0); 82 | } 83 | 84 | .odometer.odometer-auto-theme, .odometer.odometer-theme-slot-machine { 85 | -moz-border-radius: 0.34em; 86 | -webkit-border-radius: 0.34em; 87 | border-radius: 0.34em; 88 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmYwMCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2ZmYTUwMCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); 89 | background-size: 100%; 90 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffff00), color-stop(100%, #ffa500)); 91 | background-image: -moz-linear-gradient(#ffff00, #ffa500); 92 | background-image: -webkit-linear-gradient(#ffff00, #ffa500); 93 | background-image: linear-gradient(#ffff00, #ffa500); 94 | background-color: #fc0; 95 | font-family: "Rye", monospace; 96 | padding: 0.15em; 97 | color: #f80000; 98 | line-height: 1.35em; 99 | border: 0.03em solid #000; 100 | -webkit-text-stroke: 0.05em #000; 101 | } 102 | .odometer.odometer-auto-theme .odometer-digit, .odometer.odometer-theme-slot-machine .odometer-digit { 103 | -moz-box-shadow: inset 0 0 0.1em rgba(0, 0, 0, 0.5), 0 0 0 0.03em #fff, 0 0 0 0.05em rgba(0, 0, 0, 0.2); 104 | -webkit-box-shadow: inset 0 0 0.1em rgba(0, 0, 0, 0.5), 0 0 0 0.03em #fff, 0 0 0 0.05em rgba(0, 0, 0, 0.2); 105 | box-shadow: inset 0 0 0.1em rgba(0, 0, 0, 0.5), 0 0 0 0.03em #fff, 0 0 0 0.05em rgba(0, 0, 0, 0.2); 106 | -moz-border-radius: 0.2em; 107 | -webkit-border-radius: 0.2em; 108 | border-radius: 0.2em; 109 | background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2NjY2NjYyIvPjxzdG9wIG9mZnNldD0iMjAlIiBzdG9wLWNvbG9yPSIjZmZmZmZmIi8+PHN0b3Agb2Zmc2V0PSI4MCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNjY2NjY2MiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2dyYWQpIiAvPjwvc3ZnPiA='); 110 | background-size: 100%; 111 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #cccccc), color-stop(20%, #ffffff), color-stop(80%, #ffffff), color-stop(100%, #cccccc)); 112 | background-image: -moz-linear-gradient(top, #cccccc 0%, #ffffff 20%, #ffffff 80%, #cccccc 100%); 113 | background-image: -webkit-linear-gradient(top, #cccccc 0%, #ffffff 20%, #ffffff 80%, #cccccc 100%); 114 | background-image: linear-gradient(to bottom, #cccccc 0%, #ffffff 20%, #ffffff 80%, #cccccc 100%); 115 | border: 0.03em solid #444; 116 | padding: 0.1em 0.15em 0; 117 | } 118 | .odometer.odometer-auto-theme .odometer-digit:first-child, .odometer.odometer-theme-slot-machine .odometer-digit:first-child { 119 | -moz-box-shadow: inset 0.05em 0 0.1em rgba(0, 0, 0, 0.5), 0 0 0 0.03em #fff, 0 0 0 0.05em rgba(0, 0, 0, 0.2); 120 | -webkit-box-shadow: inset 0.05em 0 0.1em rgba(0, 0, 0, 0.5), 0 0 0 0.03em #fff, 0 0 0 0.05em rgba(0, 0, 0, 0.2); 121 | box-shadow: inset 0.05em 0 0.1em rgba(0, 0, 0, 0.5), 0 0 0 0.03em #fff, 0 0 0 0.05em rgba(0, 0, 0, 0.2); 122 | } 123 | .odometer.odometer-auto-theme .odometer-digit:last-child, .odometer.odometer-theme-slot-machine .odometer-digit:last-child { 124 | -moz-box-shadow: inset -0.05em 0 0.1em rgba(0, 0, 0, 0.5), 0 0 0 0.03em #fff, 0 0 0 0.05em rgba(0, 0, 0, 0.2); 125 | -webkit-box-shadow: inset -0.05em 0 0.1em rgba(0, 0, 0, 0.5), 0 0 0 0.03em #fff, 0 0 0 0.05em rgba(0, 0, 0, 0.2); 126 | box-shadow: inset -0.05em 0 0.1em rgba(0, 0, 0, 0.5), 0 0 0 0.03em #fff, 0 0 0 0.05em rgba(0, 0, 0, 0.2); 127 | } 128 | .odometer.odometer-auto-theme .odometer-digit + .odometer-digit, .odometer.odometer-theme-slot-machine .odometer-digit + .odometer-digit { 129 | margin-left: 0.15em; 130 | } 131 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-inner, .odometer.odometer-theme-slot-machine .odometer-digit .odometer-digit-inner { 132 | padding-top: 0.08em; 133 | } 134 | .odometer.odometer-auto-theme .odometer-digit .odometer-digit-inner, .odometer.odometer-auto-theme .odometer-digit .odometer-value.odometer-last-value, .odometer.odometer-theme-slot-machine .odometer-digit .odometer-digit-inner, .odometer.odometer-theme-slot-machine .odometer-digit .odometer-value.odometer-last-value { 135 | left: 0; 136 | right: 0; 137 | text-align: center; 138 | } 139 | -------------------------------------------------------------------------------- /odometer.min.js: -------------------------------------------------------------------------------- 1 | /*! odometer 0.4.8 */ 2 | (function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G=[].slice;q='',n=''+q+"",d='8'+n+"",g='',c="(,ddd).dd",h=/^\(?([^)]*)\)?(?:(.)(d+))?$/,i=30,f=2e3,a=20,j=2,e=.5,k=1e3/i,b=1e3/a,o="transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd",y=document.createElement("div").style,p=null!=y.transition||null!=y.webkitTransition||null!=y.mozTransition||null!=y.oTransition,w=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame,l=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver,s=function(a){var b;return b=document.createElement("div"),b.innerHTML=a,b.children[0]},v=function(a,b){return a.className=a.className.replace(new RegExp("(^| )"+b.split(" ").join("|")+"( |$)","gi")," ")},r=function(a,b){return v(a,b),a.className+=" "+b},z=function(a,b){var c;return null!=document.createEvent?(c=document.createEvent("HTMLEvents"),c.initEvent(b,!0,!0),a.dispatchEvent(c)):void 0},u=function(){var a,b;return null!=(a=null!=(b=window.performance)&&"function"==typeof b.now?b.now():void 0)?a:+new Date},x=function(a,b){return null==b&&(b=0),b?(a*=Math.pow(10,b),a+=.5,a=Math.floor(a),a/=Math.pow(10,b)):Math.round(a)},A=function(a){return 0>a?Math.ceil(a):Math.floor(a)},t=function(a){return a-x(a)},C=!1,(B=function(){var a,b,c,d,e;if(!C&&null!=window.jQuery){for(C=!0,d=["html","text"],e=[],b=0,c=d.length;c>b;b++)a=d[b],e.push(function(a){var b;return b=window.jQuery.fn[a],window.jQuery.fn[a]=function(a){var c;return null==a||null==(null!=(c=this[0])?c.odometer:void 0)?b.apply(this,arguments):this[0].odometer.update(a)}}(a));return e}})(),setTimeout(B,0),m=function(){function a(b){var c,d,e,g,h,i,l,m,n,o,p=this;if(this.options=b,this.el=this.options.el,null!=this.el.odometer)return this.el.odometer;this.el.odometer=this,m=a.options;for(d in m)g=m[d],null==this.options[d]&&(this.options[d]=g);null==(h=this.options).duration&&(h.duration=f),this.MAX_VALUES=this.options.duration/k/j|0,this.resetFormat(),this.value=this.cleanValue(null!=(n=this.options.value)?n:""),this.renderInside(),this.render();try{for(o=["innerHTML","innerText","textContent"],i=0,l=o.length;l>i;i++)e=o[i],null!=this.el[e]&&!function(a){return Object.defineProperty(p.el,a,{get:function(){var b;return"innerHTML"===a?p.inside.outerHTML:null!=(b=p.inside.innerText)?b:p.inside.textContent},set:function(a){return p.update(a)}})}(e)}catch(q){c=q,this.watchForMutations()}}return a.prototype.renderInside=function(){return this.inside=document.createElement("div"),this.inside.className="odometer-inside",this.el.innerHTML="",this.el.appendChild(this.inside)},a.prototype.watchForMutations=function(){var a,b=this;if(null!=l)try{return null==this.observer&&(this.observer=new l(function(a){var c;return c=b.el.innerText,b.renderInside(),b.render(b.value),b.update(c)})),this.watchMutations=!0,this.startWatchingMutations()}catch(c){a=c}},a.prototype.startWatchingMutations=function(){return this.watchMutations?this.observer.observe(this.el,{childList:!0}):void 0},a.prototype.stopWatchingMutations=function(){var a;return null!=(a=this.observer)?a.disconnect():void 0},a.prototype.cleanValue=function(a){var b;return"string"==typeof a&&(a=a.replace(null!=(b=this.format.radix)?b:".",""),a=a.replace(/[.,]/g,""),a=a.replace("","."),a=parseFloat(a,10)||0),x(a,this.format.precision)},a.prototype.bindTransitionEnd=function(){var a,b,c,d,e,f,g=this;if(!this.transitionEndBound){for(this.transitionEndBound=!0,b=!1,e=o.split(" "),f=[],c=0,d=e.length;d>c;c++)a=e[c],f.push(this.el.addEventListener(a,function(){return b?!0:(b=!0,setTimeout(function(){return g.render(),b=!1,z(g.el,"odometerdone")},0),!0)},!1));return f}},a.prototype.resetFormat=function(){var a,b,d,e,f,g,i,j;if(a=null!=(i=this.options.format)?i:c,a||(a="d"),d=h.exec(a),!d)throw new Error("Odometer: Unparsable digit format");return j=d.slice(1,4),g=j[0],f=j[1],b=j[2],e=(null!=b?b.length:void 0)||0,this.format={repeating:g,radix:f,precision:e}},a.prototype.render=function(a){var b,c,d,e,f,g,h;for(null==a&&(a=this.value),this.stopWatchingMutations(),this.resetFormat(),this.inside.innerHTML="",f=this.options.theme,b=this.el.className.split(" "),e=[],g=0,h=b.length;h>g;g++)c=b[g],c.length&&((d=/^odometer-theme-(.+)$/.exec(c))?f=d[1]:/^odometer(-|$)/.test(c)||e.push(c));return e.push("odometer"),p||e.push("odometer-no-transitions"),f?e.push("odometer-theme-"+f):e.push("odometer-auto-theme"),this.el.className=e.join(" "),this.ribbons={},this.formatDigits(a),this.startWatchingMutations()},a.prototype.formatDigits=function(a){var b,c,d,e,f,g,h,i,j,k;if(this.digits=[],this.options.formatFunction)for(d=this.options.formatFunction(a),j=d.split("").reverse(),f=0,h=j.length;h>f;f++)c=j[f],c.match(/0-9/)?(b=this.renderDigit(),b.querySelector(".odometer-value").innerHTML=c,this.digits.push(b),this.insertDigit(b)):this.addSpacer(c);else for(e=!this.format.precision||!t(a)||!1,k=a.toString().split("").reverse(),g=0,i=k.length;i>g;g++)b=k[g],"."===b&&(e=!0),this.addDigit(b,e)},a.prototype.update=function(a){var b,c=this;return a=this.cleanValue(a),(b=a-this.value)?(v(this.el,"odometer-animating-up odometer-animating-down odometer-animating"),b>0?r(this.el,"odometer-animating-up"):r(this.el,"odometer-animating-down"),this.stopWatchingMutations(),this.animate(a),this.startWatchingMutations(),setTimeout(function(){return c.el.offsetHeight,r(c.el,"odometer-animating")},0),this.value=a):void 0},a.prototype.renderDigit=function(){return s(d)},a.prototype.insertDigit=function(a,b){return null!=b?this.inside.insertBefore(a,b):this.inside.children.length?this.inside.insertBefore(a,this.inside.children[0]):this.inside.appendChild(a)},a.prototype.addSpacer=function(a,b,c){var d;return d=s(g),d.innerHTML=a,c&&r(d,c),this.insertDigit(d,b)},a.prototype.addDigit=function(a,b){var c,d,e,f;if(null==b&&(b=!0),"-"===a)return this.addSpacer(a,null,"odometer-negation-mark");if("."===a)return this.addSpacer(null!=(f=this.format.radix)?f:".",null,"odometer-radix-mark");if(b)for(e=!1;;){if(!this.format.repeating.length){if(e)throw new Error("Bad odometer format without digits");this.resetFormat(),e=!0}if(c=this.format.repeating[this.format.repeating.length-1],this.format.repeating=this.format.repeating.substring(0,this.format.repeating.length-1),"d"===c)break;this.addSpacer(c)}return d=this.renderDigit(),d.querySelector(".odometer-value").innerHTML=a,this.digits.push(d),this.insertDigit(d)},a.prototype.animate=function(a){return p&&"count"!==this.options.animation?this.animateSlide(a):this.animateCount(a)},a.prototype.animateCount=function(a){var c,d,e,f,g,h=this;if(d=+a-this.value)return f=e=u(),c=this.value,(g=function(){var i,j,k;return u()-f>h.options.duration?(h.value=a,h.render(),void z(h.el,"odometerdone")):(i=u()-e,i>b&&(e=u(),k=i/h.options.duration,j=d*k,c+=j,h.render(Math.round(c))),null!=w?w(g):setTimeout(g,b))})()},a.prototype.getDigitCount=function(){var a,b,c,d,e,f;for(d=1<=arguments.length?G.call(arguments,0):[],a=e=0,f=d.length;f>e;a=++e)c=d[a],d[a]=Math.abs(c);return b=Math.max.apply(Math,d),Math.ceil(Math.log(b+1)/Math.log(10))},a.prototype.getFractionalDigitCount=function(){var a,b,c,d,e,f,g;for(e=1<=arguments.length?G.call(arguments,0):[],b=/^\-?\d*\.(\d*?)0*$/,a=f=0,g=e.length;g>f;a=++f)d=e[a],e[a]=d.toString(),c=b.exec(e[a]),null==c?e[a]=0:e[a]=c[1].length;return Math.max.apply(Math,e)},a.prototype.resetDigits=function(){return this.digits=[],this.ribbons=[],this.inside.innerHTML="",this.resetFormat()},a.prototype.animateSlide=function(a){var b,c,d,f,g,h,i,j,k,l,m,n,o,p,q,s,t,u,v,w,x,y,z,B,C,D,E;if(s=this.value,j=this.getFractionalDigitCount(s,a),j&&(a*=Math.pow(10,j),s*=Math.pow(10,j)),d=a-s){for(this.bindTransitionEnd(),f=this.getDigitCount(s,a),g=[],b=0,m=v=0;f>=0?f>v:v>f;m=f>=0?++v:--v){if(t=A(s/Math.pow(10,f-m-1)),i=A(a/Math.pow(10,f-m-1)),h=i-t,Math.abs(h)>this.MAX_VALUES){for(l=[],n=h/(this.MAX_VALUES+this.MAX_VALUES*b*e),c=t;h>0&&i>c||0>h&&c>i;)l.push(Math.round(c)),c+=n;l[l.length-1]!==i&&l.push(i),b++}else l=function(){E=[];for(var a=t;i>=t?i>=a:a>=i;i>=t?a++:a--)E.push(a);return E}.apply(this);for(m=w=0,y=l.length;y>w;m=++w)k=l[m],l[m]=Math.abs(k%10);g.push(l)}for(this.resetDigits(),D=g.reverse(),m=x=0,z=D.length;z>x;m=++x)for(l=D[m],this.digits[m]||this.addDigit(" ",m>=j),null==(u=this.ribbons)[m]&&(u[m]=this.digits[m].querySelector(".odometer-ribbon-inner")),this.ribbons[m].innerHTML="",0>d&&(l=l.reverse()),o=C=0,B=l.length;B>C;o=++C)k=l[o],q=document.createElement("div"),q.className="odometer-value",q.innerHTML=k,this.ribbons[m].appendChild(q),o===l.length-1&&r(q,"odometer-last-value"),0===o&&r(q,"odometer-first-value");return 0>t&&this.addDigit("-"),p=this.inside.querySelector(".odometer-radix-mark"),null!=p&&p.parent.removeChild(p),j?this.addSpacer(this.format.radix,this.digits[j-1],"odometer-radix-mark"):void 0}},a}(),m.options=null!=(E=window.odometerOptions)?E:{},setTimeout(function(){var a,b,c,d,e;if(window.odometerOptions){d=window.odometerOptions,e=[];for(a in d)b=d[a],e.push(null!=(c=m.options)[a]?(c=m.options)[a]:c[a]=b);return e}},0),m.init=function(){var a,b,c,d,e,f;if(null!=document.querySelectorAll){for(b=document.querySelectorAll(m.options.selector||".odometer"),f=[],c=0,d=b.length;d>c;c++)a=b[c],f.push(a.odometer=new m({el:a,value:null!=(e=a.innerText)?e:a.textContent}));return f}},null!=(null!=(F=document.documentElement)?F.doScroll:void 0)&&null!=document.createEventObject?(D=document.onreadystatechange,document.onreadystatechange=function(){return"complete"===document.readyState&&m.options.auto!==!1&&m.init(),null!=D?D.apply(this,arguments):void 0}):document.addEventListener("DOMContentLoaded",function(){return m.options.auto!==!1?m.init():void 0},!1),"function"==typeof define&&define.amd?define([],function(){return m}):"undefined"!=typeof exports&&null!==exports?module.exports=m:window.Odometer=m}).call(this); 3 | -------------------------------------------------------------------------------- /docs/welcome/onepage-scroll.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * jquery-onepage-scroll.js v1 3 | * =========================================================== 4 | * Copyright 2013 Pete Rojwongsuriya. 5 | * http://www.thepetedesign.com 6 | * 7 | * Create an Apple-like website that let user scroll 8 | * one page at a time 9 | * 10 | * Credit: Eike Send for the awesome swipe event 11 | * https://github.com/peachananr/onepage-scroll 12 | * 13 | * ========================================================== */ 14 | 15 | !function($){ 16 | 17 | var defaults = { 18 | sectionContainer: "section", 19 | easing: "ease", 20 | animationTime: 1000, 21 | pagination: true, 22 | updateURL: false 23 | }; 24 | 25 | /*------------------------------------------------*/ 26 | /* Credit: Eike Send for the awesome swipe event */ 27 | /*------------------------------------------------*/ 28 | 29 | $.fn.swipeEvents = function() { 30 | return this.each(function() { 31 | 32 | var startX, 33 | startY, 34 | $this = $(this); 35 | 36 | $this.bind('touchstart', touchstart); 37 | 38 | function touchstart(event) { 39 | var touches = event.originalEvent.touches; 40 | if (touches && touches.length) { 41 | startX = touches[0].pageX; 42 | startY = touches[0].pageY; 43 | $this.bind('touchmove', touchmove); 44 | } 45 | event.preventDefault(); 46 | } 47 | 48 | function touchmove(event) { 49 | var touches = event.originalEvent.touches; 50 | if (touches && touches.length) { 51 | var deltaX = startX - touches[0].pageX; 52 | var deltaY = startY - touches[0].pageY; 53 | 54 | if (deltaX >= 50) { 55 | $this.trigger("swipeLeft"); 56 | } 57 | if (deltaX <= -50) { 58 | $this.trigger("swipeRight"); 59 | } 60 | if (deltaY >= 50) { 61 | $this.trigger("swipeUp"); 62 | } 63 | if (deltaY <= -50) { 64 | $this.trigger("swipeDown"); 65 | } 66 | if (Math.abs(deltaX) >= 50 || Math.abs(deltaY) >= 50) { 67 | $this.unbind('touchmove', touchmove); 68 | } 69 | } 70 | event.preventDefault(); 71 | } 72 | 73 | }); 74 | }; 75 | 76 | 77 | $.fn.onepage_scroll = function(options){ 78 | var settings = $.extend({}, defaults, options), 79 | el = $(this), 80 | sections = $(settings.sectionContainer), 81 | total = sections.length, 82 | status = "off", 83 | topPos = 0, 84 | lastAnimation = 0, 85 | quietPeriod = 500, 86 | paginationList = ""; 87 | 88 | $.fn.transformPage = function(settings, pos) { 89 | $(this).css({ 90 | "-webkit-transform": "translate3d(0, " + pos + "%, 0)", 91 | "-webkit-transition": "all " + settings.animationTime + "ms " + settings.easing, 92 | "-moz-transform": "translate3d(0, " + pos + "%, 0)", 93 | "-moz-transition": "all " + settings.animationTime + "ms " + settings.easing, 94 | "-ms-transform": "translate3d(0, " + pos + "%, 0)", 95 | "-ms-transition": "all " + settings.animationTime + "ms " + settings.easing, 96 | "transform": "translate3d(0, " + pos + "%, 0)", 97 | "transition": "all " + settings.animationTime + "ms " + settings.easing 98 | }); 99 | }; 100 | 101 | $.fn.moveDown = function() { 102 | var el = $(this); 103 | index = $(settings.sectionContainer +".active").data("index"); 104 | if(index < total) { 105 | current = $(settings.sectionContainer + "[data-index='" + index + "']"); 106 | next = $(settings.sectionContainer + "[data-index='" + (index + 1) + "']"); 107 | if(next) { 108 | current.removeClass("active"); 109 | next.addClass("active"); 110 | if(settings.pagination === true) { 111 | $(".onepage-pagination li a" + "[data-index='" + index + "']").removeClass("active"); 112 | $(".onepage-pagination li a" + "[data-index='" + (index + 1) + "']").addClass("active"); 113 | } 114 | $("body")[0].className = $("body")[0].className.replace(/\bviewing-page-\d.*?\b/g, ''); 115 | $("body").addClass("viewing-page-"+next.data("index")); 116 | 117 | if (history.replaceState && settings.updateURL === true) { 118 | var href = window.location.href.substr(0,window.location.href.indexOf('#')) + "#" + (index + 1); 119 | history.pushState( {}, document.title, href ); 120 | } 121 | } 122 | pos = (index * 100) * -1; 123 | el.transformPage(settings, pos); 124 | } 125 | }; 126 | 127 | $.fn.moveUp = function() { 128 | var el = $(this); 129 | index = $(settings.sectionContainer +".active").data("index"); 130 | if(index <= total && index > 1) { 131 | current = $(settings.sectionContainer + "[data-index='" + index + "']"); 132 | next = $(settings.sectionContainer + "[data-index='" + (index - 1) + "']"); 133 | 134 | if(next) { 135 | current.removeClass("active"); 136 | next.addClass("active"); 137 | if(settings.pagination === true) { 138 | $(".onepage-pagination li a" + "[data-index='" + index + "']").removeClass("active"); 139 | $(".onepage-pagination li a" + "[data-index='" + (index - 1) + "']").addClass("active"); 140 | } 141 | $("body")[0].className = $("body")[0].className.replace(/\bviewing-page-\d.*?\b/g, ''); 142 | $("body").addClass("viewing-page-"+next.data("index")); 143 | 144 | if (history.replaceState && settings.updateURL === true) { 145 | var href = window.location.href.substr(0,window.location.href.indexOf('#')) + "#" + (index - 1); 146 | history.pushState( {}, document.title, href ); 147 | } 148 | } 149 | pos = ((next.data("index") - 1) * 100) * -1; 150 | el.transformPage(settings, pos); 151 | } 152 | }; 153 | 154 | function init_scroll(event, delta) { 155 | deltaOfInterest = delta; 156 | var timeNow = new Date().getTime(); 157 | // Cancel scroll if currently animating or within quiet period 158 | if(timeNow - lastAnimation < quietPeriod + settings.animationTime) { 159 | event.preventDefault(); 160 | return; 161 | } 162 | 163 | if (deltaOfInterest < 0) { 164 | el.moveDown(); 165 | } else { 166 | el.moveUp(); 167 | } 168 | lastAnimation = timeNow; 169 | } 170 | 171 | // Prepare everything before binding wheel scroll 172 | 173 | el.addClass("onepage-wrapper").css("position","relative"); 174 | $.each( sections, function(i) { 175 | $(this).css({ 176 | position: "absolute", 177 | top: topPos + "%" 178 | }).addClass("section").attr("data-index", i+1); 179 | topPos = topPos + 100; 180 | if(settings.pagination === true) { 181 | paginationList += "
  • "; 182 | } 183 | }); 184 | 185 | el.swipeEvents().bind("swipeDown", function(){ 186 | el.moveUp(); 187 | }).bind("swipeUp", function(){ 188 | el.moveDown(); 189 | }); 190 | 191 | // Create Pagination and Display Them 192 | if(settings.pagination === true) { 193 | $("
      " + paginationList + "
    ").prependTo("body"); 194 | posTop = (el.find(".onepage-pagination").height() / 2) * -1; 195 | el.find(".onepage-pagination").css("margin-top", posTop); 196 | } 197 | 198 | if(window.location.hash !== "" && window.location.hash !== "#1") { 199 | init_index = window.location.hash.replace("#", ""); 200 | $(settings.sectionContainer + "[data-index='" + init_index + "']").addClass("active"); 201 | $("body").addClass("viewing-page-"+ init_index); 202 | if(settings.pagination === true) $(".onepage-pagination li a" + "[data-index='" + init_index + "']").addClass("active"); 203 | 204 | next = $(settings.sectionContainer + "[data-index='" + (init_index) + "']"); 205 | if(next) { 206 | next.addClass("active"); 207 | if(settings.pagination === true) $(".onepage-pagination li a" + "[data-index='" + (init_index) + "']").addClass("active"); 208 | $("body")[0].className = $("body")[0].className.replace(/\bviewing-page-\d.*?\b/g, ''); 209 | $("body").addClass("viewing-page-"+next.data("index")); 210 | if (history.replaceState && settings.updateURL === true) { 211 | var href = window.location.href.substr(0,window.location.href.indexOf('#')) + "#" + (init_index); 212 | history.pushState( {}, document.title, href ); 213 | } 214 | } 215 | pos = ((init_index - 1) * 100) * -1; 216 | el.transformPage(settings, pos); 217 | 218 | }else{ 219 | $(settings.sectionContainer + "[data-index='1']").addClass("active"); 220 | $("body").addClass("viewing-page-1"); 221 | if(settings.pagination === true) $(".onepage-pagination li a" + "[data-index='1']").addClass("active"); 222 | } 223 | if(settings.pagination === true) { 224 | $(".onepage-pagination li a").click(function (){ 225 | var page_index = $(this).data("index"); 226 | if (!$(this).hasClass("active")) { 227 | current = $(settings.sectionContainer + ".active"); 228 | next = $(settings.sectionContainer + "[data-index='" + (page_index) + "']"); 229 | if(next) { 230 | current.removeClass("active"); 231 | next.addClass("active"); 232 | $(".onepage-pagination li a" + ".active").removeClass("active"); 233 | $(".onepage-pagination li a" + "[data-index='" + (page_index) + "']").addClass("active"); 234 | $("body")[0].className = $("body")[0].className.replace(/\bviewing-page-\d.*?\b/g, ''); 235 | $("body").addClass("viewing-page-"+next.data("index")); 236 | } 237 | pos = ((page_index - 1) * 100) * -1; 238 | el.transformPage(settings, pos); 239 | } 240 | if (settings.updateURL === false) return false; 241 | }); 242 | } 243 | 244 | 245 | 246 | $(document).bind('mousewheel DOMMouseScroll', function(event) { 247 | event.preventDefault(); 248 | var delta = event.originalEvent.wheelDelta || -event.originalEvent.detail; 249 | init_scroll(event, delta); 250 | }); 251 | return false; 252 | 253 | }; 254 | 255 | }(window.jQuery); -------------------------------------------------------------------------------- /docs/welcome/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Odometer — Transition numbers with ease 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
    30 |
    31 |
    32 |
    33 |
    34 | O 35 |   36 | L 37 | M 38 | O 39 | 40 | D 41 |   42 | Z 43 | A 44 | B 45 | C 46 | D 47 | 48 | O 49 |   50 | I 51 | J 52 | K 53 | L 54 | M 55 | O 56 | 57 | M 58 |   59 | G 60 | H 61 | I 62 | J 63 | K 64 | L 65 | M 66 | 67 | E 68 |   69 | Y 70 | Z 71 | A 72 | B 73 | C 74 | D 75 | E 76 | 77 | T 78 |   79 | L 80 | M 81 | N 82 | O 83 | P 84 | Q 85 | R 86 | S 87 | T 88 | 89 | E 90 |   91 | V 92 | W 93 | X 94 | Y 95 | Z 96 | A 97 | B 98 | C 99 | D 100 | E 101 | 102 | R 103 |   104 | G 105 | H 106 | I 107 | J 108 | K 109 | L 110 | M 111 | O 112 | P 113 | Q 114 | R 115 | 116 |
    117 |
    118 |
    119 | Transition numbers with ease
    120 | Docs 121 | ★ On Github 122 |
    123 |
    124 |
    125 |
    126 | 127 |
    128 | 129 |
    130 |
    131 |

    How To Use

    132 |

    Add the js and a theme file to your page:

    133 |
    <link rel="stylesheet" href="odometer-theme-car.css" />
    134 | <script src="odometer.js"></script>
    135 |

    Any element with class name "odometer" will automatically be made into an Odometer! When you want to update the value, simply update it the same way you normally would.

    136 |
    element.innerHTML = 123 // Native, or...
    137 | $('.odometer').html(123) // with jQuery
    138 |
    ★ On Github
    139 |
    140 |
    141 | 142 |
    143 |
    144 |

    About

    145 |

    Odometer was made by Adam Schwartz and Zack Bloom of HubSpot.

    146 |
    ★ On Github
    147 |
    148 |
    149 | 150 |
    151 |
    152 |
    153 | 154 |

    155 |
    156 |
    157 |
    158 |
    159 |
    160 |
    161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 183 | 186 | 187 | 188 | 196 | 197 | 198 | 207 | 208 | 209 |
    210 | 211 | 212 | -------------------------------------------------------------------------------- /odometer.coffee: -------------------------------------------------------------------------------- 1 | VALUE_HTML = '' 2 | RIBBON_HTML = '' + VALUE_HTML + '' 3 | DIGIT_HTML = '8' + RIBBON_HTML + '' 4 | FORMAT_MARK_HTML = '' 5 | 6 | # The bit within the parenthesis will be repeated, so (,ddd) becomes 123,456,789.... 7 | # 8 | # If your locale uses spaces to seperate digits, you could consider using a 9 | # Narrow No-Break Space ( ), as it's a bit more correct. 10 | # 11 | # Numbers will be rounded to the number of digits after the radix seperator. 12 | # 13 | # When values are set using `.update` or the `.innerHTML`-type attributes, 14 | # strings are assumed to already be in the locale's format. 15 | # 16 | # This is just the default, it can also be set as options.format. 17 | DIGIT_FORMAT = '(,ddd).dd' 18 | 19 | FORMAT_PARSER = /^\(?([^)]*)\)?(?:(.)(d+))?$/ 20 | 21 | # What is our target framerate? 22 | FRAMERATE = 30 23 | 24 | # How long will the animation last? 25 | DURATION = 2000 26 | 27 | # What is the fastest we should update values when we are 28 | # counting up (not using the wheel animation). 29 | COUNT_FRAMERATE = 20 30 | 31 | # What is the minimum number of frames for each value on the wheel? 32 | # We won't render more values than could be reasonably seen 33 | FRAMES_PER_VALUE = 2 34 | 35 | # If more than one digit is hitting the frame limit, they would all get 36 | # capped at that limit and appear to be moving at the same rate. This 37 | # factor adds a boost to subsequent digits to make them appear faster. 38 | DIGIT_SPEEDBOOST = .5 39 | 40 | MS_PER_FRAME = 1000 / FRAMERATE 41 | COUNT_MS_PER_FRAME = 1000 / COUNT_FRAMERATE 42 | 43 | TRANSITION_END_EVENTS = 'transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd' 44 | 45 | transitionCheckStyles = document.createElement('div').style 46 | TRANSITION_SUPPORT = transitionCheckStyles.transition? or transitionCheckStyles.webkitTransition? or 47 | transitionCheckStyles.mozTransition? or transitionCheckStyles.oTransition? 48 | 49 | requestAnimationFrame = window.requestAnimationFrame or window.mozRequestAnimationFrame or 50 | window.webkitRequestAnimationFrame or window.msRequestAnimationFrame 51 | 52 | MutationObserver = window.MutationObserver or window.WebKitMutationObserver or window.MozMutationObserver 53 | 54 | createFromHTML = (html) -> 55 | el = document.createElement('div') 56 | el.innerHTML = html 57 | el.children[0] 58 | 59 | removeClass = (el, name) -> 60 | el.className = el.className.replace new RegExp("(^| )#{ name.split(' ').join('|') }( |$)", 'gi'), ' ' 61 | 62 | addClass = (el, name) -> 63 | removeClass el, name 64 | el.className += " #{ name }" 65 | 66 | trigger = (el, name) -> 67 | # Custom DOM events are not supported in IE8 68 | if document.createEvent? 69 | evt = document.createEvent('HTMLEvents') 70 | evt.initEvent(name, true, true) 71 | el.dispatchEvent(evt) 72 | 73 | now = -> 74 | window.performance?.now?() ? +new Date 75 | 76 | round = (val, precision=0) -> 77 | return Math.round(val) unless precision 78 | 79 | val *= Math.pow(10, precision) 80 | val += 0.5 81 | val = Math.floor(val) 82 | val /= Math.pow(10, precision) 83 | 84 | truncate = (val) -> 85 | # | 0 fails on numbers greater than 2^32 86 | if val < 0 87 | Math.ceil(val) 88 | else 89 | Math.floor(val) 90 | 91 | fractionalPart = (val) -> 92 | val - round(val) 93 | 94 | _jQueryWrapped = false 95 | do wrapJQuery = -> 96 | return if _jQueryWrapped 97 | 98 | if window.jQuery? 99 | _jQueryWrapped = true 100 | # We need to wrap jQuery's .html and .text because they don't always 101 | # call .innerHTML/.innerText 102 | for property in ['html', 'text'] 103 | do (property) -> 104 | old = window.jQuery.fn[property] 105 | window.jQuery.fn[property] = (val) -> 106 | if not val? or not this[0]?.odometer? 107 | return old.apply this, arguments 108 | 109 | this[0].odometer.update val 110 | 111 | # In case jQuery is brought in after this file 112 | setTimeout wrapJQuery, 0 113 | 114 | class Odometer 115 | constructor: (@options) -> 116 | @el = @options.el 117 | return @el.odometer if @el.odometer? 118 | 119 | @el.odometer = @ 120 | 121 | for k, v of Odometer.options 122 | if not @options[k]? 123 | @options[k] = v 124 | 125 | @options.duration ?= DURATION 126 | @MAX_VALUES = ((@options.duration / MS_PER_FRAME) / FRAMES_PER_VALUE) | 0 127 | 128 | @resetFormat() 129 | 130 | @value = @cleanValue(@options.value ? '') 131 | 132 | @renderInside() 133 | @render() 134 | 135 | try 136 | for property in ['innerHTML', 'innerText', 'textContent'] when @el[property]? 137 | do (property) => 138 | Object.defineProperty @el, property, 139 | get: => 140 | if property is 'innerHTML' 141 | @inside.outerHTML 142 | else 143 | # It's just a single HTML element, so innerText is the 144 | # same as outerText. 145 | @inside.innerText ? @inside.textContent 146 | set: (val) => 147 | @update val 148 | catch e 149 | # Safari 150 | @watchForMutations() 151 | 152 | @ 153 | 154 | renderInside: -> 155 | @inside = document.createElement 'div' 156 | @inside.className = 'odometer-inside' 157 | @el.innerHTML = '' 158 | @el.appendChild @inside 159 | 160 | watchForMutations: -> 161 | # Safari doesn't allow us to wrap .innerHTML, so we listen for it 162 | # changing. 163 | return unless MutationObserver? 164 | 165 | try 166 | @observer ?= new MutationObserver (mutations) => 167 | newVal = @el.innerText 168 | 169 | @renderInside() 170 | @render @value 171 | @update newVal 172 | 173 | @watchMutations = true 174 | @startWatchingMutations() 175 | catch e 176 | 177 | startWatchingMutations: -> 178 | if @watchMutations 179 | @observer.observe @el, {childList: true} 180 | 181 | stopWatchingMutations: -> 182 | @observer?.disconnect() 183 | 184 | cleanValue: (val) -> 185 | if typeof val is 'string' 186 | # We need to normalize the format so we can properly turn it into 187 | # a float. 188 | val = val.replace((@format.radix ? '.'), '') 189 | val = val.replace /[.,]/g, '' 190 | val = val.replace '', '.' 191 | val = parseFloat(val, 10) or 0 192 | 193 | round(val, @format.precision) 194 | 195 | bindTransitionEnd: -> 196 | return if @transitionEndBound 197 | @transitionEndBound = true 198 | 199 | # The event will be triggered once for each ribbon, we only 200 | # want one render though 201 | renderEnqueued = false 202 | for event in TRANSITION_END_EVENTS.split(' ') 203 | @el.addEventListener event, => 204 | return true if renderEnqueued 205 | 206 | renderEnqueued = true 207 | 208 | setTimeout => 209 | @render() 210 | renderEnqueued = false 211 | 212 | trigger @el, 'odometerdone' 213 | , 0 214 | 215 | true 216 | , false 217 | 218 | resetFormat: -> 219 | format = @options.format ? DIGIT_FORMAT 220 | format or= 'd' 221 | 222 | parsed = FORMAT_PARSER.exec format 223 | if not parsed 224 | throw new Error "Odometer: Unparsable digit format" 225 | 226 | [repeating, radix, fractional] = parsed[1..3] 227 | 228 | precision = fractional?.length or 0 229 | 230 | @format = {repeating, radix, precision} 231 | 232 | render: (value=@value) -> 233 | @stopWatchingMutations() 234 | @resetFormat() 235 | 236 | @inside.innerHTML = '' 237 | 238 | theme = @options.theme 239 | 240 | classes = @el.className.split(' ') 241 | newClasses = [] 242 | for cls in classes when cls.length 243 | if match = /^odometer-theme-(.+)$/.exec(cls) 244 | theme = match[1] 245 | continue 246 | 247 | if /^odometer(-|$)/.test(cls) 248 | continue 249 | 250 | newClasses.push cls 251 | 252 | newClasses.push 'odometer' 253 | 254 | unless TRANSITION_SUPPORT 255 | newClasses.push 'odometer-no-transitions' 256 | 257 | if theme 258 | newClasses.push "odometer-theme-#{ theme }" 259 | else 260 | # This class matches all themes, so it should do what you'd expect if only one 261 | # theme css file is brought into the page. 262 | newClasses.push "odometer-auto-theme" 263 | 264 | @el.className = newClasses.join(' ') 265 | 266 | @ribbons = {} 267 | 268 | @formatDigits(value) 269 | 270 | @startWatchingMutations() 271 | 272 | formatDigits: (value) -> 273 | @digits = [] 274 | 275 | if @options.formatFunction 276 | valueString = @options.formatFunction(value) 277 | for valueDigit in valueString.split('').reverse() 278 | if valueDigit.match(/0-9/) 279 | digit = @renderDigit() 280 | digit.querySelector('.odometer-value').innerHTML = valueDigit 281 | @digits.push digit 282 | @insertDigit digit 283 | else 284 | @addSpacer valueDigit 285 | else 286 | wholePart = not @format.precision or not fractionalPart(value) or false 287 | for digit in value.toString().split('').reverse() 288 | if digit is '.' 289 | wholePart = true 290 | 291 | @addDigit digit, wholePart 292 | 293 | return 294 | 295 | update: (newValue) -> 296 | newValue = @cleanValue newValue 297 | 298 | return unless diff = newValue - @value 299 | 300 | removeClass @el, 'odometer-animating-up odometer-animating-down odometer-animating' 301 | if diff > 0 302 | addClass @el, 'odometer-animating-up' 303 | else 304 | addClass @el, 'odometer-animating-down' 305 | 306 | @stopWatchingMutations() 307 | @animate newValue 308 | @startWatchingMutations() 309 | 310 | setTimeout => 311 | # Force a repaint 312 | @el.offsetHeight 313 | 314 | addClass @el, 'odometer-animating' 315 | , 0 316 | 317 | @value = newValue 318 | 319 | renderDigit: -> 320 | createFromHTML DIGIT_HTML 321 | 322 | insertDigit: (digit, before) -> 323 | if before? 324 | @inside.insertBefore digit, before 325 | else if not @inside.children.length 326 | @inside.appendChild digit 327 | else 328 | @inside.insertBefore digit, @inside.children[0] 329 | 330 | addSpacer: (chr, before, extraClasses) -> 331 | spacer = createFromHTML FORMAT_MARK_HTML 332 | spacer.innerHTML = chr 333 | addClass(spacer, extraClasses) if extraClasses 334 | @insertDigit spacer, before 335 | 336 | addDigit: (value, repeating=true) -> 337 | if value is '-' 338 | return @addSpacer value, null, 'odometer-negation-mark' 339 | 340 | if value is '.' 341 | return @addSpacer (@format.radix ? '.'), null, 'odometer-radix-mark' 342 | 343 | if repeating 344 | resetted = false 345 | while true 346 | if not @format.repeating.length 347 | if resetted 348 | throw new Error "Bad odometer format without digits" 349 | 350 | @resetFormat() 351 | resetted = true 352 | 353 | chr = @format.repeating[@format.repeating.length - 1] 354 | @format.repeating = @format.repeating.substring(0, @format.repeating.length - 1) 355 | 356 | break if chr is 'd' 357 | 358 | @addSpacer chr 359 | 360 | digit = @renderDigit() 361 | digit.querySelector('.odometer-value').innerHTML = value 362 | @digits.push digit 363 | 364 | @insertDigit digit 365 | 366 | animate: (newValue) -> 367 | if not TRANSITION_SUPPORT or @options.animation is 'count' 368 | @animateCount newValue 369 | else 370 | @animateSlide newValue 371 | 372 | animateCount: (newValue) -> 373 | return unless diff = +newValue - @value 374 | 375 | start = last = now() 376 | 377 | cur = @value 378 | do tick = => 379 | if (now() - start) > @options.duration 380 | @value = newValue 381 | @render() 382 | trigger @el, 'odometerdone' 383 | return 384 | 385 | delta = now() - last 386 | 387 | if delta > COUNT_MS_PER_FRAME 388 | last = now() 389 | 390 | fraction = delta / @options.duration 391 | dist = diff * fraction 392 | 393 | cur += dist 394 | @render Math.round cur 395 | 396 | if requestAnimationFrame? 397 | requestAnimationFrame tick 398 | else 399 | setTimeout tick, COUNT_MS_PER_FRAME 400 | 401 | getDigitCount: (values...) -> 402 | for value, i in values 403 | values[i] = Math.abs(value) 404 | 405 | max = Math.max values... 406 | 407 | Math.ceil(Math.log(max + 1) / Math.log(10)) 408 | 409 | getFractionalDigitCount: (values...) -> 410 | # This assumes the value has already been rounded to 411 | # @format.precision places 412 | # 413 | parser = /^\-?\d*\.(\d*?)0*$/ 414 | for value, i in values 415 | values[i] = value.toString() 416 | 417 | parts = parser.exec values[i] 418 | 419 | if not parts? 420 | values[i] = 0 421 | else 422 | values[i] = parts[1].length 423 | 424 | Math.max values... 425 | 426 | resetDigits: -> 427 | @digits = [] 428 | @ribbons = [] 429 | @inside.innerHTML = '' 430 | @resetFormat() 431 | 432 | animateSlide: (newValue) -> 433 | oldValue = @value 434 | 435 | fractionalCount = @getFractionalDigitCount oldValue, newValue 436 | 437 | if fractionalCount 438 | newValue = newValue * Math.pow(10, fractionalCount) 439 | oldValue = oldValue * Math.pow(10, fractionalCount) 440 | 441 | return unless diff = newValue - oldValue 442 | 443 | @bindTransitionEnd() 444 | 445 | digitCount = @getDigitCount(oldValue, newValue) 446 | 447 | digits = [] 448 | boosted = 0 449 | # We create a array to represent the series of digits which should be 450 | # animated in each column 451 | for i in [0...digitCount] 452 | start = truncate(oldValue / Math.pow(10, (digitCount - i - 1))) 453 | end = truncate(newValue / Math.pow(10, (digitCount - i - 1))) 454 | 455 | dist = end - start 456 | 457 | if Math.abs(dist) > @MAX_VALUES 458 | # We need to subsample 459 | frames = [] 460 | 461 | # Subsequent digits need to be faster than previous ones 462 | incr = dist / (@MAX_VALUES + @MAX_VALUES * boosted * DIGIT_SPEEDBOOST) 463 | cur = start 464 | 465 | while (dist > 0 and cur < end) or (dist < 0 and cur > end) 466 | frames.push Math.round cur 467 | cur += incr 468 | 469 | if frames[frames.length - 1] isnt end 470 | frames.push end 471 | 472 | boosted++ 473 | else 474 | frames = [start..end] 475 | 476 | # We only care about the last digit 477 | for frame, i in frames 478 | frames[i] = Math.abs(frame % 10) 479 | 480 | digits.push frames 481 | 482 | @resetDigits() 483 | 484 | for frames, i in digits.reverse() 485 | if not @digits[i] 486 | @addDigit ' ', (i >= fractionalCount) 487 | 488 | @ribbons[i] ?= @digits[i].querySelector('.odometer-ribbon-inner') 489 | @ribbons[i].innerHTML = '' 490 | 491 | if diff < 0 492 | frames = frames.reverse() 493 | 494 | for frame, j in frames 495 | numEl = document.createElement('div') 496 | numEl.className = 'odometer-value' 497 | numEl.innerHTML = frame 498 | 499 | @ribbons[i].appendChild numEl 500 | 501 | if j == frames.length - 1 502 | addClass numEl, 'odometer-last-value' 503 | if j == 0 504 | addClass numEl, 'odometer-first-value' 505 | 506 | if start < 0 507 | @addDigit '-' 508 | 509 | mark = @inside.querySelector('.odometer-radix-mark') 510 | mark.parent.removeChild(mark) if mark? 511 | 512 | if fractionalCount 513 | @addSpacer @format.radix, @digits[fractionalCount - 1], 'odometer-radix-mark' 514 | 515 | Odometer.options = window.odometerOptions ? {} 516 | 517 | setTimeout -> 518 | # We do this in a seperate pass to allow people to set 519 | # window.odometerOptions after bringing the file in. 520 | if window.odometerOptions 521 | for k, v of window.odometerOptions 522 | Odometer.options[k] ?= v 523 | , 0 524 | 525 | Odometer.init = -> 526 | if not document.querySelectorAll? 527 | # IE 7 or 8 in Quirksmode 528 | return 529 | 530 | elements = document.querySelectorAll (Odometer.options.selector or '.odometer') 531 | 532 | for el in elements 533 | el.odometer = new Odometer {el, value: (el.innerText ? el.textContent)} 534 | 535 | if document.documentElement?.doScroll? and document.createEventObject? 536 | # IE < 9 537 | _old = document.onreadystatechange 538 | document.onreadystatechange = -> 539 | if document.readyState is 'complete' and Odometer.options.auto isnt false 540 | Odometer.init() 541 | 542 | _old?.apply this, arguments 543 | else 544 | document.addEventListener 'DOMContentLoaded', -> 545 | if Odometer.options.auto isnt false 546 | Odometer.init() 547 | , false 548 | 549 | 550 | if typeof define is 'function' and define.amd 551 | # AMD. Register as an anonymous module. 552 | define [], -> 553 | Odometer 554 | else if exports? 555 | # CommonJS 556 | module.exports = Odometer 557 | else 558 | # Browser globals 559 | window.Odometer = Odometer 560 | -------------------------------------------------------------------------------- /odometer.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var COUNT_FRAMERATE, COUNT_MS_PER_FRAME, DIGIT_FORMAT, DIGIT_HTML, DIGIT_SPEEDBOOST, DURATION, FORMAT_MARK_HTML, FORMAT_PARSER, FRAMERATE, FRAMES_PER_VALUE, MS_PER_FRAME, MutationObserver, Odometer, RIBBON_HTML, TRANSITION_END_EVENTS, TRANSITION_SUPPORT, VALUE_HTML, addClass, createFromHTML, fractionalPart, now, removeClass, requestAnimationFrame, round, transitionCheckStyles, trigger, truncate, wrapJQuery, _jQueryWrapped, _old, _ref, _ref1, 3 | __slice = [].slice; 4 | 5 | VALUE_HTML = ''; 6 | 7 | RIBBON_HTML = '' + VALUE_HTML + ''; 8 | 9 | DIGIT_HTML = '8' + RIBBON_HTML + ''; 10 | 11 | FORMAT_MARK_HTML = ''; 12 | 13 | DIGIT_FORMAT = '(,ddd).dd'; 14 | 15 | FORMAT_PARSER = /^\(?([^)]*)\)?(?:(.)(d+))?$/; 16 | 17 | FRAMERATE = 30; 18 | 19 | DURATION = 2000; 20 | 21 | COUNT_FRAMERATE = 20; 22 | 23 | FRAMES_PER_VALUE = 2; 24 | 25 | DIGIT_SPEEDBOOST = .5; 26 | 27 | MS_PER_FRAME = 1000 / FRAMERATE; 28 | 29 | COUNT_MS_PER_FRAME = 1000 / COUNT_FRAMERATE; 30 | 31 | TRANSITION_END_EVENTS = 'transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd'; 32 | 33 | transitionCheckStyles = document.createElement('div').style; 34 | 35 | TRANSITION_SUPPORT = (transitionCheckStyles.transition != null) || (transitionCheckStyles.webkitTransition != null) || (transitionCheckStyles.mozTransition != null) || (transitionCheckStyles.oTransition != null); 36 | 37 | requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; 38 | 39 | MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 40 | 41 | createFromHTML = function(html) { 42 | var el; 43 | el = document.createElement('div'); 44 | el.innerHTML = html; 45 | return el.children[0]; 46 | }; 47 | 48 | removeClass = function(el, name) { 49 | return el.className = el.className.replace(new RegExp("(^| )" + (name.split(' ').join('|')) + "( |$)", 'gi'), ' '); 50 | }; 51 | 52 | addClass = function(el, name) { 53 | removeClass(el, name); 54 | return el.className += " " + name; 55 | }; 56 | 57 | trigger = function(el, name) { 58 | var evt; 59 | if (document.createEvent != null) { 60 | evt = document.createEvent('HTMLEvents'); 61 | evt.initEvent(name, true, true); 62 | return el.dispatchEvent(evt); 63 | } 64 | }; 65 | 66 | now = function() { 67 | var _ref, _ref1; 68 | return (_ref = (_ref1 = window.performance) != null ? typeof _ref1.now === "function" ? _ref1.now() : void 0 : void 0) != null ? _ref : +(new Date); 69 | }; 70 | 71 | round = function(val, precision) { 72 | if (precision == null) { 73 | precision = 0; 74 | } 75 | if (!precision) { 76 | return Math.round(val); 77 | } 78 | val *= Math.pow(10, precision); 79 | val += 0.5; 80 | val = Math.floor(val); 81 | return val /= Math.pow(10, precision); 82 | }; 83 | 84 | truncate = function(val) { 85 | if (val < 0) { 86 | return Math.ceil(val); 87 | } else { 88 | return Math.floor(val); 89 | } 90 | }; 91 | 92 | fractionalPart = function(val) { 93 | return val - round(val); 94 | }; 95 | 96 | _jQueryWrapped = false; 97 | 98 | (wrapJQuery = function() { 99 | var property, _i, _len, _ref, _results; 100 | if (_jQueryWrapped) { 101 | return; 102 | } 103 | if (window.jQuery != null) { 104 | _jQueryWrapped = true; 105 | _ref = ['html', 'text']; 106 | _results = []; 107 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 108 | property = _ref[_i]; 109 | _results.push((function(property) { 110 | var old; 111 | old = window.jQuery.fn[property]; 112 | return window.jQuery.fn[property] = function(val) { 113 | var _ref1; 114 | if ((val == null) || (((_ref1 = this[0]) != null ? _ref1.odometer : void 0) == null)) { 115 | return old.apply(this, arguments); 116 | } 117 | return this[0].odometer.update(val); 118 | }; 119 | })(property)); 120 | } 121 | return _results; 122 | } 123 | })(); 124 | 125 | setTimeout(wrapJQuery, 0); 126 | 127 | Odometer = (function() { 128 | function Odometer(options) { 129 | var e, k, property, v, _base, _i, _len, _ref, _ref1, _ref2, 130 | _this = this; 131 | this.options = options; 132 | this.el = this.options.el; 133 | if (this.el.odometer != null) { 134 | return this.el.odometer; 135 | } 136 | this.el.odometer = this; 137 | _ref = Odometer.options; 138 | for (k in _ref) { 139 | v = _ref[k]; 140 | if (this.options[k] == null) { 141 | this.options[k] = v; 142 | } 143 | } 144 | if ((_base = this.options).duration == null) { 145 | _base.duration = DURATION; 146 | } 147 | this.MAX_VALUES = ((this.options.duration / MS_PER_FRAME) / FRAMES_PER_VALUE) | 0; 148 | this.resetFormat(); 149 | this.value = this.cleanValue((_ref1 = this.options.value) != null ? _ref1 : ''); 150 | this.renderInside(); 151 | this.render(); 152 | try { 153 | _ref2 = ['innerHTML', 'innerText', 'textContent']; 154 | for (_i = 0, _len = _ref2.length; _i < _len; _i++) { 155 | property = _ref2[_i]; 156 | if (this.el[property] != null) { 157 | (function(property) { 158 | return Object.defineProperty(_this.el, property, { 159 | get: function() { 160 | var _ref3; 161 | if (property === 'innerHTML') { 162 | return _this.inside.outerHTML; 163 | } else { 164 | return (_ref3 = _this.inside.innerText) != null ? _ref3 : _this.inside.textContent; 165 | } 166 | }, 167 | set: function(val) { 168 | return _this.update(val); 169 | } 170 | }); 171 | })(property); 172 | } 173 | } 174 | } catch (_error) { 175 | e = _error; 176 | this.watchForMutations(); 177 | } 178 | this; 179 | } 180 | 181 | Odometer.prototype.renderInside = function() { 182 | this.inside = document.createElement('div'); 183 | this.inside.className = 'odometer-inside'; 184 | this.el.innerHTML = ''; 185 | return this.el.appendChild(this.inside); 186 | }; 187 | 188 | Odometer.prototype.watchForMutations = function() { 189 | var e, 190 | _this = this; 191 | if (MutationObserver == null) { 192 | return; 193 | } 194 | try { 195 | if (this.observer == null) { 196 | this.observer = new MutationObserver(function(mutations) { 197 | var newVal; 198 | newVal = _this.el.innerText; 199 | _this.renderInside(); 200 | _this.render(_this.value); 201 | return _this.update(newVal); 202 | }); 203 | } 204 | this.watchMutations = true; 205 | return this.startWatchingMutations(); 206 | } catch (_error) { 207 | e = _error; 208 | } 209 | }; 210 | 211 | Odometer.prototype.startWatchingMutations = function() { 212 | if (this.watchMutations) { 213 | return this.observer.observe(this.el, { 214 | childList: true 215 | }); 216 | } 217 | }; 218 | 219 | Odometer.prototype.stopWatchingMutations = function() { 220 | var _ref; 221 | return (_ref = this.observer) != null ? _ref.disconnect() : void 0; 222 | }; 223 | 224 | Odometer.prototype.cleanValue = function(val) { 225 | var _ref; 226 | if (typeof val === 'string') { 227 | val = val.replace((_ref = this.format.radix) != null ? _ref : '.', ''); 228 | val = val.replace(/[.,]/g, ''); 229 | val = val.replace('', '.'); 230 | val = parseFloat(val, 10) || 0; 231 | } 232 | return round(val, this.format.precision); 233 | }; 234 | 235 | Odometer.prototype.bindTransitionEnd = function() { 236 | var event, renderEnqueued, _i, _len, _ref, _results, 237 | _this = this; 238 | if (this.transitionEndBound) { 239 | return; 240 | } 241 | this.transitionEndBound = true; 242 | renderEnqueued = false; 243 | _ref = TRANSITION_END_EVENTS.split(' '); 244 | _results = []; 245 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 246 | event = _ref[_i]; 247 | _results.push(this.el.addEventListener(event, function() { 248 | if (renderEnqueued) { 249 | return true; 250 | } 251 | renderEnqueued = true; 252 | setTimeout(function() { 253 | _this.render(); 254 | renderEnqueued = false; 255 | return trigger(_this.el, 'odometerdone'); 256 | }, 0); 257 | return true; 258 | }, false)); 259 | } 260 | return _results; 261 | }; 262 | 263 | Odometer.prototype.resetFormat = function() { 264 | var format, fractional, parsed, precision, radix, repeating, _ref, _ref1; 265 | format = (_ref = this.options.format) != null ? _ref : DIGIT_FORMAT; 266 | format || (format = 'd'); 267 | parsed = FORMAT_PARSER.exec(format); 268 | if (!parsed) { 269 | throw new Error("Odometer: Unparsable digit format"); 270 | } 271 | _ref1 = parsed.slice(1, 4), repeating = _ref1[0], radix = _ref1[1], fractional = _ref1[2]; 272 | precision = (fractional != null ? fractional.length : void 0) || 0; 273 | return this.format = { 274 | repeating: repeating, 275 | radix: radix, 276 | precision: precision 277 | }; 278 | }; 279 | 280 | Odometer.prototype.render = function(value) { 281 | var classes, cls, match, newClasses, theme, _i, _len; 282 | if (value == null) { 283 | value = this.value; 284 | } 285 | this.stopWatchingMutations(); 286 | this.resetFormat(); 287 | this.inside.innerHTML = ''; 288 | theme = this.options.theme; 289 | classes = this.el.className.split(' '); 290 | newClasses = []; 291 | for (_i = 0, _len = classes.length; _i < _len; _i++) { 292 | cls = classes[_i]; 293 | if (!cls.length) { 294 | continue; 295 | } 296 | if (match = /^odometer-theme-(.+)$/.exec(cls)) { 297 | theme = match[1]; 298 | continue; 299 | } 300 | if (/^odometer(-|$)/.test(cls)) { 301 | continue; 302 | } 303 | newClasses.push(cls); 304 | } 305 | newClasses.push('odometer'); 306 | if (!TRANSITION_SUPPORT) { 307 | newClasses.push('odometer-no-transitions'); 308 | } 309 | if (theme) { 310 | newClasses.push("odometer-theme-" + theme); 311 | } else { 312 | newClasses.push("odometer-auto-theme"); 313 | } 314 | this.el.className = newClasses.join(' '); 315 | this.ribbons = {}; 316 | this.formatDigits(value); 317 | return this.startWatchingMutations(); 318 | }; 319 | 320 | Odometer.prototype.formatDigits = function(value) { 321 | var digit, valueDigit, valueString, wholePart, _i, _j, _len, _len1, _ref, _ref1; 322 | this.digits = []; 323 | if (this.options.formatFunction) { 324 | valueString = this.options.formatFunction(value); 325 | _ref = valueString.split('').reverse(); 326 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 327 | valueDigit = _ref[_i]; 328 | if (valueDigit.match(/0-9/)) { 329 | digit = this.renderDigit(); 330 | digit.querySelector('.odometer-value').innerHTML = valueDigit; 331 | this.digits.push(digit); 332 | this.insertDigit(digit); 333 | } else { 334 | this.addSpacer(valueDigit); 335 | } 336 | } 337 | } else { 338 | wholePart = !this.format.precision || !fractionalPart(value) || false; 339 | _ref1 = value.toString().split('').reverse(); 340 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { 341 | digit = _ref1[_j]; 342 | if (digit === '.') { 343 | wholePart = true; 344 | } 345 | this.addDigit(digit, wholePart); 346 | } 347 | } 348 | }; 349 | 350 | Odometer.prototype.update = function(newValue) { 351 | var diff, 352 | _this = this; 353 | newValue = this.cleanValue(newValue); 354 | if (!(diff = newValue - this.value)) { 355 | return; 356 | } 357 | removeClass(this.el, 'odometer-animating-up odometer-animating-down odometer-animating'); 358 | if (diff > 0) { 359 | addClass(this.el, 'odometer-animating-up'); 360 | } else { 361 | addClass(this.el, 'odometer-animating-down'); 362 | } 363 | this.stopWatchingMutations(); 364 | this.animate(newValue); 365 | this.startWatchingMutations(); 366 | setTimeout(function() { 367 | _this.el.offsetHeight; 368 | return addClass(_this.el, 'odometer-animating'); 369 | }, 0); 370 | return this.value = newValue; 371 | }; 372 | 373 | Odometer.prototype.renderDigit = function() { 374 | return createFromHTML(DIGIT_HTML); 375 | }; 376 | 377 | Odometer.prototype.insertDigit = function(digit, before) { 378 | if (before != null) { 379 | return this.inside.insertBefore(digit, before); 380 | } else if (!this.inside.children.length) { 381 | return this.inside.appendChild(digit); 382 | } else { 383 | return this.inside.insertBefore(digit, this.inside.children[0]); 384 | } 385 | }; 386 | 387 | Odometer.prototype.addSpacer = function(chr, before, extraClasses) { 388 | var spacer; 389 | spacer = createFromHTML(FORMAT_MARK_HTML); 390 | spacer.innerHTML = chr; 391 | if (extraClasses) { 392 | addClass(spacer, extraClasses); 393 | } 394 | return this.insertDigit(spacer, before); 395 | }; 396 | 397 | Odometer.prototype.addDigit = function(value, repeating) { 398 | var chr, digit, resetted, _ref; 399 | if (repeating == null) { 400 | repeating = true; 401 | } 402 | if (value === '-') { 403 | return this.addSpacer(value, null, 'odometer-negation-mark'); 404 | } 405 | if (value === '.') { 406 | return this.addSpacer((_ref = this.format.radix) != null ? _ref : '.', null, 'odometer-radix-mark'); 407 | } 408 | if (repeating) { 409 | resetted = false; 410 | while (true) { 411 | if (!this.format.repeating.length) { 412 | if (resetted) { 413 | throw new Error("Bad odometer format without digits"); 414 | } 415 | this.resetFormat(); 416 | resetted = true; 417 | } 418 | chr = this.format.repeating[this.format.repeating.length - 1]; 419 | this.format.repeating = this.format.repeating.substring(0, this.format.repeating.length - 1); 420 | if (chr === 'd') { 421 | break; 422 | } 423 | this.addSpacer(chr); 424 | } 425 | } 426 | digit = this.renderDigit(); 427 | digit.querySelector('.odometer-value').innerHTML = value; 428 | this.digits.push(digit); 429 | return this.insertDigit(digit); 430 | }; 431 | 432 | Odometer.prototype.animate = function(newValue) { 433 | if (!TRANSITION_SUPPORT || this.options.animation === 'count') { 434 | return this.animateCount(newValue); 435 | } else { 436 | return this.animateSlide(newValue); 437 | } 438 | }; 439 | 440 | Odometer.prototype.animateCount = function(newValue) { 441 | var cur, diff, last, start, tick, 442 | _this = this; 443 | if (!(diff = +newValue - this.value)) { 444 | return; 445 | } 446 | start = last = now(); 447 | cur = this.value; 448 | return (tick = function() { 449 | var delta, dist, fraction; 450 | if ((now() - start) > _this.options.duration) { 451 | _this.value = newValue; 452 | _this.render(); 453 | trigger(_this.el, 'odometerdone'); 454 | return; 455 | } 456 | delta = now() - last; 457 | if (delta > COUNT_MS_PER_FRAME) { 458 | last = now(); 459 | fraction = delta / _this.options.duration; 460 | dist = diff * fraction; 461 | cur += dist; 462 | _this.render(Math.round(cur)); 463 | } 464 | if (requestAnimationFrame != null) { 465 | return requestAnimationFrame(tick); 466 | } else { 467 | return setTimeout(tick, COUNT_MS_PER_FRAME); 468 | } 469 | })(); 470 | }; 471 | 472 | Odometer.prototype.getDigitCount = function() { 473 | var i, max, value, values, _i, _len; 474 | values = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 475 | for (i = _i = 0, _len = values.length; _i < _len; i = ++_i) { 476 | value = values[i]; 477 | values[i] = Math.abs(value); 478 | } 479 | max = Math.max.apply(Math, values); 480 | return Math.ceil(Math.log(max + 1) / Math.log(10)); 481 | }; 482 | 483 | Odometer.prototype.getFractionalDigitCount = function() { 484 | var i, parser, parts, value, values, _i, _len; 485 | values = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 486 | parser = /^\-?\d*\.(\d*?)0*$/; 487 | for (i = _i = 0, _len = values.length; _i < _len; i = ++_i) { 488 | value = values[i]; 489 | values[i] = value.toString(); 490 | parts = parser.exec(values[i]); 491 | if (parts == null) { 492 | values[i] = 0; 493 | } else { 494 | values[i] = parts[1].length; 495 | } 496 | } 497 | return Math.max.apply(Math, values); 498 | }; 499 | 500 | Odometer.prototype.resetDigits = function() { 501 | this.digits = []; 502 | this.ribbons = []; 503 | this.inside.innerHTML = ''; 504 | return this.resetFormat(); 505 | }; 506 | 507 | Odometer.prototype.animateSlide = function(newValue) { 508 | var boosted, cur, diff, digitCount, digits, dist, end, fractionalCount, frame, frames, i, incr, j, mark, numEl, oldValue, start, _base, _i, _j, _k, _l, _len, _len1, _len2, _m, _ref, _results; 509 | oldValue = this.value; 510 | fractionalCount = this.getFractionalDigitCount(oldValue, newValue); 511 | if (fractionalCount) { 512 | newValue = newValue * Math.pow(10, fractionalCount); 513 | oldValue = oldValue * Math.pow(10, fractionalCount); 514 | } 515 | if (!(diff = newValue - oldValue)) { 516 | return; 517 | } 518 | this.bindTransitionEnd(); 519 | digitCount = this.getDigitCount(oldValue, newValue); 520 | digits = []; 521 | boosted = 0; 522 | for (i = _i = 0; 0 <= digitCount ? _i < digitCount : _i > digitCount; i = 0 <= digitCount ? ++_i : --_i) { 523 | start = truncate(oldValue / Math.pow(10, digitCount - i - 1)); 524 | end = truncate(newValue / Math.pow(10, digitCount - i - 1)); 525 | dist = end - start; 526 | if (Math.abs(dist) > this.MAX_VALUES) { 527 | frames = []; 528 | incr = dist / (this.MAX_VALUES + this.MAX_VALUES * boosted * DIGIT_SPEEDBOOST); 529 | cur = start; 530 | while ((dist > 0 && cur < end) || (dist < 0 && cur > end)) { 531 | frames.push(Math.round(cur)); 532 | cur += incr; 533 | } 534 | if (frames[frames.length - 1] !== end) { 535 | frames.push(end); 536 | } 537 | boosted++; 538 | } else { 539 | frames = (function() { 540 | _results = []; 541 | for (var _j = start; start <= end ? _j <= end : _j >= end; start <= end ? _j++ : _j--){ _results.push(_j); } 542 | return _results; 543 | }).apply(this); 544 | } 545 | for (i = _k = 0, _len = frames.length; _k < _len; i = ++_k) { 546 | frame = frames[i]; 547 | frames[i] = Math.abs(frame % 10); 548 | } 549 | digits.push(frames); 550 | } 551 | this.resetDigits(); 552 | _ref = digits.reverse(); 553 | for (i = _l = 0, _len1 = _ref.length; _l < _len1; i = ++_l) { 554 | frames = _ref[i]; 555 | if (!this.digits[i]) { 556 | this.addDigit(' ', i >= fractionalCount); 557 | } 558 | if ((_base = this.ribbons)[i] == null) { 559 | _base[i] = this.digits[i].querySelector('.odometer-ribbon-inner'); 560 | } 561 | this.ribbons[i].innerHTML = ''; 562 | if (diff < 0) { 563 | frames = frames.reverse(); 564 | } 565 | for (j = _m = 0, _len2 = frames.length; _m < _len2; j = ++_m) { 566 | frame = frames[j]; 567 | numEl = document.createElement('div'); 568 | numEl.className = 'odometer-value'; 569 | numEl.innerHTML = frame; 570 | this.ribbons[i].appendChild(numEl); 571 | if (j === frames.length - 1) { 572 | addClass(numEl, 'odometer-last-value'); 573 | } 574 | if (j === 0) { 575 | addClass(numEl, 'odometer-first-value'); 576 | } 577 | } 578 | } 579 | if (start < 0) { 580 | this.addDigit('-'); 581 | } 582 | mark = this.inside.querySelector('.odometer-radix-mark'); 583 | if (mark != null) { 584 | mark.parent.removeChild(mark); 585 | } 586 | if (fractionalCount) { 587 | return this.addSpacer(this.format.radix, this.digits[fractionalCount - 1], 'odometer-radix-mark'); 588 | } 589 | }; 590 | 591 | return Odometer; 592 | 593 | })(); 594 | 595 | Odometer.options = (_ref = window.odometerOptions) != null ? _ref : {}; 596 | 597 | setTimeout(function() { 598 | var k, v, _base, _ref1, _results; 599 | if (window.odometerOptions) { 600 | _ref1 = window.odometerOptions; 601 | _results = []; 602 | for (k in _ref1) { 603 | v = _ref1[k]; 604 | _results.push((_base = Odometer.options)[k] != null ? (_base = Odometer.options)[k] : _base[k] = v); 605 | } 606 | return _results; 607 | } 608 | }, 0); 609 | 610 | Odometer.init = function() { 611 | var el, elements, _i, _len, _ref1, _results; 612 | if (document.querySelectorAll == null) { 613 | return; 614 | } 615 | elements = document.querySelectorAll(Odometer.options.selector || '.odometer'); 616 | _results = []; 617 | for (_i = 0, _len = elements.length; _i < _len; _i++) { 618 | el = elements[_i]; 619 | _results.push(el.odometer = new Odometer({ 620 | el: el, 621 | value: (_ref1 = el.innerText) != null ? _ref1 : el.textContent 622 | })); 623 | } 624 | return _results; 625 | }; 626 | 627 | if ((((_ref1 = document.documentElement) != null ? _ref1.doScroll : void 0) != null) && (document.createEventObject != null)) { 628 | _old = document.onreadystatechange; 629 | document.onreadystatechange = function() { 630 | if (document.readyState === 'complete' && Odometer.options.auto !== false) { 631 | Odometer.init(); 632 | } 633 | return _old != null ? _old.apply(this, arguments) : void 0; 634 | }; 635 | } else { 636 | document.addEventListener('DOMContentLoaded', function() { 637 | if (Odometer.options.auto !== false) { 638 | return Odometer.init(); 639 | } 640 | }, false); 641 | } 642 | 643 | if (typeof define === 'function' && define.amd) { 644 | define([], function() { 645 | return Odometer; 646 | }); 647 | } else if (typeof exports !== "undefined" && exports !== null) { 648 | module.exports = Odometer; 649 | } else { 650 | window.Odometer = Odometer; 651 | } 652 | 653 | }).call(this); 654 | --------------------------------------------------------------------------------