├── CHANGELOG
├── digits.css
├── README.md
├── digits-default.css
├── digits-white.css
└── digits.js
/CHANGELOG:
--------------------------------------------------------------------------------
1 |
2 | Upcoming Versions
3 | -----------------
4 |
5 | - More skins
6 |
7 | - Fire events on specific ranges
8 |
9 | - Use the Page Visiblity API
10 |
11 | - Responsive
12 |
13 | - IE Support + Safari 5 + Others
14 |
15 |
16 |
17 | 0.9.1 (December 6, 2012)
18 | -----------------------
19 |
20 | - Fixed initial delay -- timer may be off by a few milliseconds
21 |
22 | - Cleanup
23 |
24 |
25 |
26 | 0.9 (November 20, 2012)
27 | -----------------------
28 |
29 | - Countdown and Statistics mode
30 |
--------------------------------------------------------------------------------
/digits.css:
--------------------------------------------------------------------------------
1 |
2 | /* CORE CSS AND ANIMATIONS
3 | -------------------------------------------------*/
4 |
5 | .digits,
6 | .digits .digit {
7 | display: inline-block;
8 | }
9 |
10 | .digits .top-half-wrapper,
11 | .digits .bottom-half-wrapper {
12 | position: relative;
13 | }
14 |
15 | .digits .top-half,
16 | .digits .bottom-half {
17 | position: absolute;
18 | overflow: hidden;
19 | top: 0;
20 | left: 0;
21 | }
22 |
23 | .digits .top-half {
24 | -webkit-transform-origin: 50% 100%;
25 | -moz-transform-origin: 50% 100%;
26 |
27 | -webkit-transform: perspective(300) rotateX(0deg);
28 | -moz-transform: rotateX(0deg);
29 | }
30 |
31 | .digits .bottom-half {
32 | line-height: 0;
33 |
34 | -webkit-transform-origin: 50% 0%;
35 | -moz-transform-origin: 50% 0%;
36 |
37 | -webkit-transform: perspective(300) rotateX(90deg);
38 | -moz-transform: rotateX(90deg);
39 | }
40 |
41 | .digits .no-animation {
42 | -webkit-transition: none !important;
43 | -moz-transition: none !important;
44 | transition: none !important;
45 | }
46 |
47 | .digits .show {
48 | z-index: 10;
49 |
50 | -webkit-transform: perspective(300) rotateX(0deg);
51 | -moz-transform: rotateX(0deg);
52 | }
53 |
54 | .digits .roll-over {
55 | -webkit-transform: perspective(300) rotateX(-90deg);
56 | -moz-transform: rotateX(-90deg);
57 | }
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | Digits
3 | ====
4 |
5 | [Demo](http://www.kennethcachia.com/digits)
6 |
7 | Includes
8 | ---
9 |
10 | **Core Files**
11 |
12 |
13 |
14 |
15 |
16 | **Skins**
17 |
18 | Choose one from the following skins:
19 |
20 |
21 |
22 |
23 |
24 | Mode 1: Countdown
25 | ---
26 |
27 | Countdown to a specific date.
28 |
29 | new Digits({
30 | wrapper: '#wrapper',
31 | mode: 'countdown',
32 | to: 'January 1 2013 00:00:00',
33 | labels: true,
34 | ready: function () {
35 | alert('Happy New Year!');
36 | }
37 | });
38 |
39 | **wrapper**: class/ID of (empty) element where digits are displayed
40 |
41 | **to**: countdown to this date
42 |
43 | **labels**: show/hide labels (Days, Hours, Minutes, Seconds)
44 |
45 | **ready**: function to be executed when counter reaches zero
46 |
47 | Mode 2: Statistics
48 | ---
49 |
50 | Start off with an initial value. Can also be changed at runtime.
51 |
52 | var stats = new Digits({
53 | wrapper: '#wrapper',
54 | mode: 'statistics',
55 | value: 199
56 | });
57 |
58 | **wrapper**: class/ID of (empty) element where digits are displayed
59 |
60 | **value**: initial value
61 |
62 |
63 | Value can be changed at runtime using:
64 |
65 | stats.changeValue(240);
66 |
67 |
--------------------------------------------------------------------------------
/digits-default.css:
--------------------------------------------------------------------------------
1 |
2 | /* DEFAULT SKIN
3 | -------------------------------------------------*/
4 |
5 | .digits .top-half-wrapper,
6 | .digits .bottom-half-wrapper {
7 | width: 100px;
8 | height: 50px;
9 | }
10 |
11 | .digits .top-half,
12 | .digits .bottom-half {
13 | width: 100px;
14 | height: 50px;
15 | text-shadow: 0px 1px 0px #000;
16 | text-align: center;
17 | font-family: helvetica, sans;
18 | font-weight: bold;
19 | font-size: 80px;
20 | }
21 |
22 | .digits .top-half {
23 | color: #fff;
24 | }
25 |
26 | .digits .bottom-half {
27 | color: #f1f1f1;
28 | }
29 |
30 | .digits .top-half {
31 | background: #525252;
32 | border-radius: 10px 10px 0px 0px;
33 | line-height: 100px;
34 | }
35 |
36 | .digits .bottom-half {
37 | background: #333;
38 | border-radius: 0px 0px 10px 10px ;
39 | }
40 |
41 |
42 | /* COUNTDOWN MODE
43 | -------------------------------------------------*/
44 |
45 | .digits.countdown .digit:not(:last-child) {
46 | margin-right: 10px;
47 | }
48 |
49 | .digits.countdown .digit.second-2 {
50 | margin-right: 0;
51 | }
52 |
53 | .digits.countdown .digit.second-1,
54 | .digits.countdown .digit.minute-1,
55 | .digits.countdown .digit.hour-1 {
56 | margin-left: 50px;
57 | }
58 |
59 |
60 | /* STATISTICS MODE
61 | -------------------------------------------------*/
62 |
63 | .digits.statistics .digit:not(:last-of-type) {
64 | margin-right: 5px;
65 | }
66 |
67 |
68 | /* LABELS
69 | -------------------------------------------------*/
70 |
71 | .digits .labels {
72 | text-align: right;
73 | color: #111;
74 | }
75 |
76 | .digits .label {
77 | display: inline-block;
78 | width: 210px;
79 | text-align: center;
80 | font-family: helvetica, sans;
81 | font-size: 11px;
82 | letter-spacing: 1px;
83 | font-weight: bold;
84 | text-transform: uppercase;
85 | margin: 20px 0;
86 | }
87 |
88 | .digits .label.hours,
89 | .digits .label.minutes,
90 | .digits .label.seconds {
91 | margin-left: 60px;
92 | }
93 |
94 | .digits .label.days:after {
95 | content: "Days";
96 | }
97 |
98 | .digits .label.hours:after {
99 | content: "Hours";
100 | }
101 |
102 | .digits .label.minutes:after {
103 | content: "Minutes";
104 | }
105 |
106 | .digits .label.seconds:after {
107 | content: "Seconds";
108 | }
109 |
--------------------------------------------------------------------------------
/digits-white.css:
--------------------------------------------------------------------------------
1 |
2 | /* DEFAULT SKIN
3 | -------------------------------------------------*/
4 |
5 | .digits .top-half-wrapper,
6 | .digits .bottom-half-wrapper {
7 | width: 100px;
8 | height: 50px;
9 | }
10 |
11 | .digits .top-half,
12 | .digits .bottom-half {
13 | width: 100px;
14 | height: 50px;
15 | text-shadow: 0px 1px 0px #fafafa;
16 | text-align: center;
17 | font-family: helvetica, sans;
18 | font-weight: bold;
19 | font-size: 80px;
20 | }
21 |
22 | .digits .top-half {
23 | color: #333;
24 | }
25 |
26 | .digits .bottom-half {
27 | color: #222;
28 | }
29 |
30 | .digits .top-half {
31 | background: #fff;
32 | border-radius: 10px 10px 0px 0px;
33 | line-height: 100px;
34 | }
35 |
36 | .digits .bottom-half {
37 | background: #e5e5e5;
38 | border-radius: 0px 0px 10px 10px ;
39 | }
40 |
41 |
42 | /* COUNTDOWN MODE
43 | -------------------------------------------------*/
44 |
45 | .digits.countdown .digit:not(:last-child) {
46 | margin-right: 10px;
47 | }
48 |
49 | .digits.countdown .digit.second-2 {
50 | margin-right: 0;
51 | }
52 |
53 | .digits.countdown .digit.second-1,
54 | .digits.countdown .digit.minute-1,
55 | .digits.countdown .digit.hour-1 {
56 | margin-left: 50px;
57 | }
58 |
59 |
60 | /* STATISTICS MODE
61 | -------------------------------------------------*/
62 |
63 | .digits.statistics .digit:not(:last-of-type) {
64 | margin-right: 5px;
65 | }
66 |
67 |
68 | /* LABELS
69 | -------------------------------------------------*/
70 |
71 | .digits .labels {
72 | text-align: right;
73 | color: #111;
74 | }
75 |
76 | .digits .label {
77 | display: inline-block;
78 | width: 210px;
79 | text-align: center;
80 | font-family: helvetica, sans;
81 | font-size: 11px;
82 | letter-spacing: 1px;
83 | font-weight: bold;
84 | text-transform: uppercase;
85 | margin: 20px 0;
86 | }
87 |
88 | .digits .label.hours,
89 | .digits .label.minutes,
90 | .digits .label.seconds {
91 | margin-left: 60px;
92 | }
93 |
94 | .digits .label.days:after {
95 | content: "Days";
96 | }
97 |
98 | .digits .label.hours:after {
99 | content: "Hours";
100 | }
101 |
102 | .digits .label.minutes:after {
103 | content: "Minutes";
104 | }
105 |
106 | .digits .label.seconds:after {
107 | content: "Seconds";
108 | }
109 |
--------------------------------------------------------------------------------
/digits.js:
--------------------------------------------------------------------------------
1 |
2 | // Basic JS Operations
3 | var JS = {
4 | createElement: function (params) {
5 | var el = document.createElement(params.type),
6 | i;
7 |
8 | if (params.classes) {
9 | for (i = 0; i < params.classes.length; i += 1) {
10 | el.classList.add(params.classes[i]);
11 | }
12 | }
13 |
14 | if (params.styles) {
15 | for (i = 0; i < params.styles.length; i += 1) {
16 | el.style[params.styles[i][0]] = params.styles[i][1];
17 | }
18 | }
19 |
20 | if (params.parent) {
21 | params.parent.appendChild(el);
22 | }
23 |
24 | return el;
25 | },
26 |
27 | class: function (params) {
28 | var els = params.parent.querySelectorAll(params.selector),
29 | e;
30 |
31 | for (e = 0; e < els.length; e += 1) {
32 | if (params.mode === 'remove') {
33 | els[e].classList.remove(params.className);
34 | } else if (params.mode === 'add') {
35 | els[e].classList.add(params.className);
36 | }
37 | }
38 | }
39 | };
40 |
41 |
42 | // Individual Digit
43 | var Digit = function (params) {
44 | var current,
45 | topHalfWrapper,
46 | bottomHalfWrapper,
47 | digitWrapper,
48 | ripple,
49 | interval;
50 |
51 | function createDigit(no) {
52 | var topHalf,
53 | bottomHalf;
54 |
55 | topHalf = JS.createElement({
56 | parent: topHalfWrapper,
57 | type: 'div',
58 | classes: ['no-' + no, 'top-half'],
59 | styles: [
60 | ['webkitTransition', 'all ' + params.animationDelay + ' linear'],
61 | ['MozTransition', 'all ' + params.animationDelay + ' linear']
62 | ]
63 | });
64 |
65 | bottomHalf = JS.createElement({
66 | parent: bottomHalfWrapper,
67 | type: 'div',
68 | classes: ['no-' + no, 'bottom-half'],
69 | styles: [
70 | ['zIndex', 10 - no],
71 | ['webkitTransition', 'all ' + params.animationDelay + ' linear'],
72 | ['webkitTransitionDelay', params.animationDelay],
73 | ['MozTransition', 'all ' + params.animationDelay + ' linear'],
74 | ['MozTransitionDelay', params.animationDelay]
75 | ]
76 | });
77 |
78 | bottomHalf.innerHTML = no;
79 | topHalf.innerHTML = no;
80 |
81 | if (no === params.start) {
82 | topHalf.classList.add('show');
83 | bottomHalf.classList.add('show');
84 | }
85 | }
86 |
87 | function getMax() {
88 | var max;
89 |
90 | if (params.group && ripple) {
91 | max = ripple.isMax() ? params.max : 9;
92 | } else {
93 | max = params.max;
94 | }
95 |
96 | return max;
97 | }
98 |
99 | function cleanFrame(f, type) {
100 | if (f > getMax()) {
101 | f = 0;
102 | }
103 |
104 | JS.class({ parent: digitWrapper, selector: type + '.no-' + f, className: 'no-animation', mode: 'add' });
105 | JS.class({ parent: digitWrapper, selector: type + '.no-' + f, className: 'show', mode: 'remove' });
106 | JS.class({ parent: digitWrapper, selector: type + '.no-' + f, className: 'roll-over', mode: 'remove' });
107 | }
108 |
109 | function bottomHalfZeroZindex(i) {
110 | if (i === 0) {
111 | digitWrapper.querySelector('.bottom-half.no-' + getMax()).style.zIndex = 999;
112 | }
113 | }
114 |
115 | function tickTock() {
116 | if (current === 0) {
117 | digitWrapper.querySelector('.bottom-half.no-' + getMax()).style.zIndex = 1;
118 | }
119 |
120 | // Reset Top Half
121 | setTimeout(function () {
122 | cleanFrame(current + 1, '.top-half');
123 | }, parseFloat(params.animationDelay) * 1000);
124 |
125 | // Reset bottom half
126 | setTimeout(function () {
127 | cleanFrame(current + 1, '.bottom-half');
128 | bottomHalfZeroZindex(current);
129 | }, parseFloat(params.animationDelay) * 1000 * 2);
130 |
131 | // Prepare for next animation
132 | setTimeout(function () {
133 | JS.class({ parent: digitWrapper, selector: '.top-half', className: 'no-animation', mode: 'remove' });
134 | JS.class({ parent: digitWrapper, selector:'.bottom-half', className: 'no-animation', mode: 'remove' });
135 | }, parseFloat(params.animationDelay) * 1000 * 3);
136 |
137 | // Animate top half
138 | JS.class({ parent: digitWrapper, selector: '.top-half.no-' + current, className: 'roll-over', mode: 'add' });
139 |
140 | // Affect nearby digits
141 | if (ripple && current === 0) {
142 | ripple.flip();
143 | }
144 |
145 | // Next cycle
146 | current = current === 0 ? getMax() : --current;
147 |
148 | // Finish animation
149 | JS.class({ parent: digitWrapper, selector: '.no-' + current, className: 'show', mode: 'add' });
150 |
151 | // Stop Timer
152 | if (params.play && ripple && current === 0) {
153 |
154 | if (ripple.isReady()) {
155 | params.ready();
156 | clearInterval(interval);
157 | }
158 | }
159 | }
160 |
161 | function build() {
162 | var d;
163 |
164 | if (params) {
165 | params.max = params.max || 9;
166 | params.delay = params.delay || 1000;
167 | params.first = params.first || false;
168 | params.start = params.start >= 0 ? params.start : params.max;
169 | params.group = params.group || false;
170 | params.play = params.play || false;
171 | params.animationDelay = params.animationDelay || '.20s';
172 | params.name = params.name || [];
173 | params.offset = params.offset || 0;
174 |
175 | params.name.push('digit');
176 | current = params.start;
177 |
178 | } else {
179 | throw new Error('Params not Defined');
180 | }
181 |
182 | digitWrapper = JS.createElement({ type: 'div', classes: params.name });
183 | topHalfWrapper = JS.createElement({ parent: digitWrapper, type: 'div', classes: ['top-half-wrapper'] });
184 | bottomHalfWrapper = JS.createElement({ parent: digitWrapper, type: 'div', classes: ['bottom-half-wrapper'] });
185 |
186 | for (d = 0; d <= 9; d += 1) {
187 | createDigit(d);
188 | }
189 |
190 | if (params.first && params.wrapper.childNodes[0]) {
191 | params.wrapper.insertBefore(digitWrapper, params.wrapper.childNodes[0]);
192 | } else {
193 | params.wrapper.appendChild(digitWrapper);
194 | }
195 |
196 | bottomHalfZeroZindex(params.start);
197 | }
198 |
199 | this.isMax = function () {
200 | return current === params.max;
201 | }
202 |
203 | this.isZero = function () {
204 | return current === 0 ? true : false;
205 | }
206 |
207 | this.isReady = function () {
208 | return ripple ? this.isZero() && ripple.isReady() : this.isZero();
209 | }
210 |
211 | this.affect = function (digit) {
212 | ripple = digit;
213 | }
214 |
215 | this.flip = function (forceValue) {
216 | if (forceValue || forceValue === 0) {
217 |
218 | interval = setInterval(function() {
219 | if (current != forceValue) {
220 | tickTock();
221 | } else {
222 | clearInterval(interval);
223 | }
224 | }, params.delay);
225 |
226 | } else {
227 | tickTock();
228 | }
229 | }
230 |
231 | function animate() {
232 | if (params.play) {
233 | setTimeout(function () {
234 | interval = setInterval(function () {
235 | tickTock();
236 | }, params.delay);
237 | }, params.offset);
238 | }
239 | }
240 |
241 | build();
242 | animate();
243 | }
244 |
245 |
246 | // Main Function
247 | var Digits = function (params) {
248 | var digits = [],
249 | classes = {
250 | main: 'digits',
251 | countdown: 'countdown',
252 | statistics: 'statistics'
253 | };
254 |
255 | this.changeValue = function(newValue) {
256 | var p = digits.length - 1;
257 | newValue = newValue && newValue.toString() || '0';
258 |
259 | // Set new value
260 | for (var d = newValue.length - 1; d >= 0; d--) {
261 | if (digits[p]) {
262 | digits[p].flip(newValue[d]);
263 | } else {
264 | digits = digits.reverse();
265 | digits.push(new Digit({ name: ['statistic', 'statistic-' + (d + 1)], first: true, wrapper: params.wrapper, start: parseInt(newValue[d]), delay: 250, animationDelay: '.08s' }));
266 | digits = digits.reverse();
267 | }
268 |
269 | p--;
270 | }
271 |
272 | // Clear unwanted digits
273 | for (var d = 0; d <= digits.length - newValue.length - 1; d++) {
274 | digits[d].flip(0);
275 | }
276 | }
277 |
278 | function statistics() {
279 | if (!params.value) {
280 | throw Error('Missng value parameter');
281 | }
282 |
283 | params.value = params.value.toString();
284 |
285 | for (var d = 0; d< params.value.length; d++) {
286 | digits.push(new Digit({ name: ['statistic', 'statistic-' + (d + 1)], wrapper: params.wrapper, start: parseInt(params.value[d]), delay: 250, animationDelay: '.08s' }));
287 | }
288 | }
289 |
290 | function countdown() {
291 | var days,
292 | hours,
293 | minutes,
294 | seconds,
295 | labels,
296 | offset,
297 | diff;
298 |
299 | if (!params.to) {
300 | throw Error('Missing to parameter');
301 | }
302 |
303 | // Time difference
304 | diff = (new Date(params.to) - new Date());
305 |
306 | if (diff > 0) {
307 |
308 | // TODO: IMPROVE
309 | days = Math.floor(diff / 1000 / 60 / 60 / 24).toString();
310 | hours = Math.floor((diff / 1000 / 60 / 60) - (days * 24)).toString();
311 | minutes = Math.floor(((diff / 1000 / 60 / 60) - (days * 24) - hours) * 60).toString();
312 | seconds = Math.floor(((((diff / 1000 / 60 / 60) - (days * 24) - hours) * 60) - minutes) * 60).toString();
313 |
314 | // Fix initial out-of-sync delay
315 | offset = diff % 1000;
316 |
317 | // Add Leading zeros
318 | hours = hours.length === 1 ? '0' + hours : hours;
319 | minutes = minutes.length === 1 ? '0' + minutes : minutes;
320 | seconds = seconds.length === 1 ? '0' + seconds : seconds;
321 |
322 | } else {
323 | days = hours = minutes = seconds = '00';
324 | }
325 |
326 | // Days
327 | for (var d = 0; d < days.length; d++) {
328 | digits.push(new Digit({ name: ['day', 'day-' + (d + 1)], ready: params.ready, wrapper: params.wrapper, start: parseInt(days[d]) }));
329 | }
330 |
331 | // Hours
332 | digits.push(new Digit({ name: ['hour', 'hour-1'], ready: params.ready, wrapper: params.wrapper, start: parseInt(hours[0]), max: 2 }));
333 | digits.push(new Digit({ name: ['hour', 'hour-2'], ready: params.ready, wrapper: params.wrapper, start: parseInt(hours[1]), max: 3, group: true }));
334 |
335 | // Minutes
336 | digits.push(new Digit({ name: ['minute', 'minute-1'], ready: params.ready, wrapper: params.wrapper, start: parseInt(minutes[0]), max: 5 }));
337 | digits.push(new Digit({ name: ['minute', 'minute-2'], ready: params.ready, wrapper: params.wrapper, start: parseInt(minutes[1]) }));
338 |
339 | // Seconds
340 | digits.push(new Digit({ name: ['second', 'second-1'], ready: params.ready, wrapper: params.wrapper, start: parseInt(seconds[0]), max: 5 }));
341 | digits.push(new Digit({ name: ['second', 'second-2'], ready: params.ready, wrapper: params.wrapper, start: parseInt(seconds[1]), play: diff > 0 ? true : false, offset: offset }));
342 |
343 | // Fire ready event when countdown is not required
344 | if (diff <= 0) {
345 | params.ready();
346 | }
347 |
348 | // Add labels
349 | if (params.labels) {
350 | labels = JS.createElement({ type: 'div', parent: params.wrapper, classes: ['labels'] });
351 |
352 | JS.createElement({ parent: labels, classes: ['label', 'days'], type: 'span' })
353 | JS.createElement({ parent: labels, classes: ['label', 'hours'], type: 'span' })
354 | JS.createElement({ parent: labels, classes: ['label', 'minutes'], type: 'span' })
355 | JS.createElement({ parent: labels, classes: ['label', 'seconds'], type: 'span' })
356 | }
357 |
358 | // Link digits
359 | for (var d = digits.length - 1; d > 0; d--) {
360 | digits[d].affect(digits[d-1]);
361 | }
362 | }
363 |
364 | function init() {
365 | // Check params
366 | if (params) {
367 | params.wrapper = params.wrapper && document.querySelector(params.wrapper);
368 | params.mode = params.mode || 'countdown';
369 | params.labels = params.labels || false;
370 |
371 | if (!params.wrapper) {
372 | throw Error('Missing parameters');
373 | } else {
374 | params.wrapper.innerHTML = '';
375 | params.wrapper = JS.createElement({ parent: params.wrapper, type: 'div', classes: [classes.main] });
376 | }
377 | } else {
378 | throw Error('Params not defined');
379 | }
380 |
381 | // Main Class
382 | params.wrapper.classList.add(classes.main);
383 |
384 | // Plugin mode
385 | switch (params.mode) {
386 | case 'countdown':
387 | params.wrapper.classList.add(classes.countdown);
388 | countdown();
389 | break;
390 |
391 | case 'statistics':
392 | params.wrapper.classList.add(classes.statistics);
393 | statistics();
394 | break;
395 |
396 | default:
397 | break;
398 | }
399 | }
400 |
401 | init();
402 | }
403 |
--------------------------------------------------------------------------------