jquery.time-to.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. /**
  2. * Time-To jQuery plug-in
  3. * Show countdown timer or realtime clock
  4. *
  5. * @author Alexey Teterin <altmoc@gmail.com>
  6. * @version 1.1.2
  7. * @license MIT http://opensource.org/licenses/MIT
  8. * @date 2015-11-26
  9. */
  10. 'use strict';
  11. (function(factory) {
  12. if(typeof exports === 'object') {
  13. // CommonJS (Node)
  14. var jQuery = require('jquery');
  15. module.exports = factory(jQuery || $);
  16. } else if(typeof define === 'function' && define.amd) {
  17. // AMD (RequireJS)
  18. define(['jquery'], factory);
  19. } else {
  20. // globals
  21. factory(jQuery || $);
  22. }
  23. }(function($) {
  24. var SECONDS_PER_DAY = 86400,
  25. SECONDS_PER_HOUR = 3600;
  26. var defaults = {
  27. callback: null, // callback function for exec when timer out
  28. captionSize: 0, // font-size by pixels for captions, if 0 then calculate automaticaly
  29. countdown: true, // is countdown or real clock
  30. countdownAlertLimit: 10, // limit in seconds when display red background
  31. displayCaptions: false, // display captions under digit groups
  32. displayDays: 0, // display day timer, count of days digits
  33. displayHours: true, // display hours
  34. fontFamily: 'Verdana, sans-serif',
  35. fontSize: 0, // font-size of a digit by pixels (0 - use CSS instead)
  36. lang: 'en', // language of caption
  37. seconds: 0, // timer's countdown value in seconds
  38. start: true, // true to start timer immediately
  39. theme: 'white', // 'white' or 'black' theme fo timer's view
  40. width: 25, // width of a digit area
  41. height: 30, // height of a digit area
  42. gap: 11, // gap size between numbers
  43. vals: [0, 0, 0, 0, 0, 0, 0, 0, 0], // private, current value of each digit
  44. limits: [9, 9, 9, 2, 9, 5, 9, 5, 9],// private, max value of each digit
  45. iSec: 8, // private, index of second digit
  46. iHour: 4, // private, index of hour digit
  47. tickTimeout: 1000, // timeout betweet each timer tick in miliseconds
  48. intervalId: null // private
  49. };
  50. var methods = {
  51. start: function(sec) {
  52. var me = this,
  53. intervalId;
  54. if(sec) {
  55. init.call(this, sec);
  56. intervalId = setTimeout(function() { tick.call(me); }, 1000);
  57. // save start time
  58. this.data('ttStartTime', $.now());
  59. this.data('intervalId', intervalId);
  60. }
  61. },
  62. stop: function() {
  63. var data = this.data();
  64. if(data.intervalId) {
  65. clearTimeout(data.intervalId);
  66. this.data('intervalId', null);
  67. }
  68. return data;
  69. },
  70. reset: function(sec) {
  71. var data = methods.stop.call(this);
  72. this.find('div').css({ backgroundPosition: 'left center' });
  73. this.find('ul').parent().removeClass('timeTo-alert');
  74. if(typeof sec === 'undefined') {
  75. sec = data.seconds;
  76. }
  77. init.call(this, sec, true);
  78. }
  79. };
  80. var dictionary = {
  81. en:{days:'days', hours:'hours', min:'minutes', sec:'seconds'},
  82. ru:{days:'дней', hours:'часов', min:'минут', sec:'секунд'},
  83. ua:{days:'днiв', hours:'годин', min:'хвилин', sec:'секунд'},
  84. de:{days:'Tag', hours:'Uhr', min:'Minuten', sec:'Secunden'},
  85. fr:{days:'jours', hours:'heures', min:'minutes', sec:'secondes'},
  86. sp:{days:'días', hours:'horas', min:'minutos', sec:'segundos'},
  87. it:{days:'giorni', hours:'ore', min:'minuti', sec:'secondi'},
  88. nl:{days:'dagen', hours:'uren', min:'minuten', sec:'seconden'},
  89. no:{days:'dager', hours:'timer', min:'minutter', sec:'sekunder'},
  90. pt:{days:'dias', hours:'horas', min:'minutos', sec:'segundos'},
  91. tr:{days:'gün', hours:'saat', min:'dakika', sec:'saniye'}
  92. };
  93. if(typeof $.support.transition === 'undefined') {
  94. $.support.transition = (function() {
  95. var thisBody = document.body || document.documentElement,
  96. thisStyle = thisBody.style,
  97. support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined;
  98. return support;
  99. })();
  100. }
  101. $.fn.timeTo = function() {
  102. var method, options = {};
  103. var i, arg, num;
  104. var time, days, now = $.now();
  105. var tt, sec, m, t;
  106. for(i = 0; arg = arguments[i]; ++i) {
  107. if(i == 0 && typeof arg === 'string') {
  108. method = arg;
  109. }
  110. else {
  111. if(typeof arg === 'object') {
  112. // arg is a Date object
  113. if(typeof arg.getTime === 'function') {
  114. options.timeTo = arg;
  115. }
  116. else { // arg is an options object
  117. options = $.extend(options, arg);
  118. }
  119. }
  120. else {
  121. // arg is callback
  122. if(typeof arg === 'function') {
  123. options.callback = arg;
  124. }
  125. else {
  126. num = parseInt(arg, 10);
  127. // arg is seconds of timeout
  128. if(!isNaN(num)) {
  129. options.seconds = num;
  130. }
  131. }
  132. }
  133. }
  134. }
  135. // set time to countdown to
  136. if(options.timeTo) {
  137. if(options.timeTo.getTime) { // set time as date object
  138. time = options.timeTo.getTime();
  139. }
  140. else if(typeof options.timeTo === 'number') { // set time as integer in millisec
  141. time = options.timeTo;
  142. }
  143. if(time > now) {
  144. options.seconds = Math.floor((time - now) / 1000);
  145. }
  146. else {
  147. options.seconds = 0;
  148. }
  149. } else if(options.time || !options.seconds) {
  150. time = options.time;
  151. if(!time) {
  152. time = new Date();
  153. }
  154. if(typeof time === 'object' && time.getTime) {
  155. options.seconds = time.getHours()*SECONDS_PER_HOUR + time.getMinutes()*60 + time.getSeconds();
  156. options.countdown = false;
  157. }
  158. else if(typeof time === 'string') {
  159. tt = time.split(':');
  160. sec = 0;
  161. m = 1;
  162. while(t = tt.pop()) {
  163. sec += t * m;
  164. m *= 60;
  165. }
  166. options.seconds = sec;
  167. options.countdown = false;
  168. }
  169. }
  170. if(options.countdown !== false && options.seconds > SECONDS_PER_DAY && typeof options.displayDays === 'undefined') {
  171. days = Math.floor(options.seconds / SECONDS_PER_DAY);
  172. options.displayDays = days < 10 && 1 || days < 100 && 2 || 3;
  173. }
  174. else if(options.displayDays === true) {
  175. options.displayDays = 3;
  176. }
  177. else if(options.displayDays) {
  178. options.displayDays = options.displayDays > 0 ? Math.floor(options.displayDays) : 3;
  179. }
  180. return this.each(function() {
  181. var $this = $(this),
  182. data = $this.data(),
  183. opt, defs = {}, i, css;
  184. if(data.intervalId) {
  185. clearInterval(data.intervalId);
  186. data.intervalId = null;
  187. }
  188. if(!data.vals) { // new clock
  189. if(data.opt) {
  190. opt = data.options;
  191. }
  192. else {
  193. opt = options;
  194. }
  195. // clone the defaults object
  196. for(i in defaults) {
  197. if($.isArray(defaults[i])) {
  198. defs[i] = defaults[i].slice(0);
  199. }
  200. else {
  201. defs[i] = defaults[i];
  202. }
  203. }
  204. data = $.extend(defs, opt);
  205. data.options = opt;
  206. data.height = Math.round(data.fontSize * 100 / 93) || data.height;
  207. data.width = Math.round(data.fontSize * 0.8 + data.height * 0.13) || data.width;
  208. data.displayHours = !!(data.displayDays || data.displayHours);
  209. css = {
  210. fontFamily: data.fontFamily
  211. };
  212. if(data.fontSize > 0) {
  213. css.fontSize = data.fontSize +'px';
  214. }
  215. $this
  216. .addClass('timeTo')
  217. .addClass('timeTo-'+ data.theme)
  218. .css(css);
  219. var left = Math.round(data.height / 10),
  220. ulhtml = '<ul style="left:'+ left +'px; top:-'+ data.height +'px"><li>0</li><li>0</li></ul></div>',
  221. style = data.fontSize ? ' style="width:'+ data.width +'px; height:'+ data.height +'px;"' : ' style=""',
  222. dhtml1 = '<div class="first"'+ style +'>'+ ulhtml,
  223. dhtml2 = '<div'+ style +'>'+ ulhtml,
  224. dot2 = '<span>:</span>',
  225. maxWidth = Math.round(data.width * 2 + 3),
  226. captionSize = data.captionSize || data.fontSize && Math.round(data.fontSize * 0.43),
  227. fsStyleVal = captionSize ? 'font-size:'+ captionSize +'px;' : '',
  228. fsStyle = captionSize ? ' style="'+ fsStyleVal +'"' : '',
  229. thtml = (data.displayCaptions ?
  230. (data.displayHours
  231. ? '<figure style="max-width:'+ maxWidth +'px">$1<figcaption'+ fsStyle +'>'+ dictionary[data.lang].hours +'</figcaption></figure>'+ dot2
  232. : '') +
  233. '<figure style="max-width:'+ maxWidth +'px">$1<figcaption'+ fsStyle +'>'+ dictionary[data.lang].min +'</figcaption></figure>'+ dot2 +
  234. '<figure style="max-width:'+ maxWidth +'px">$1<figcaption'+ fsStyle +'>'+ dictionary[data.lang].sec +'</figcaption></figure>'
  235. : (data.displayHours ? '$1'+ dot2 : '') +'$1'+ dot2 +'$1'
  236. ).replace(/\$1/g, dhtml1 + dhtml2);
  237. if(data.displayDays > 0) {
  238. var marginRight = data.fontSize * 0.4 || defaults.gap,
  239. dhtml = dhtml1;
  240. for(i = data.displayDays - 1; i > 0; i--) {
  241. dhtml += i === 1 ? dhtml2.replace('">', 'margin-right:'+ Math.round(marginRight) +'px">') : dhtml2;
  242. }
  243. thtml = (data.displayCaptions ?
  244. '<figure style="width:'+ Math.round(data.width*data.displayDays + marginRight + 4) +'px">$1'
  245. + '<figcaption style="'+ fsStyleVal +'padding-right:'+ Math.round(marginRight) +'px">'
  246. + dictionary[data.lang].days +'</figcaption></figure>'
  247. : '$1').replace(
  248. /\$1/, dhtml
  249. ) + thtml;
  250. }
  251. $this.html(thtml);
  252. }
  253. else if(method !== 'reset') { // exists clock
  254. $.extend(data, options);
  255. }
  256. var $digits = $this.find('div');
  257. if($digits.length < data.vals.length) {
  258. var dif = data.vals.length - $digits.length,
  259. vals = data.vals, limits = data.limits;
  260. data.vals = [];
  261. data.limits = [];
  262. for(i = 0; i < $digits.length; i++) {
  263. data.vals[i] = vals[dif + i];
  264. data.limits[i] = limits[dif + i];
  265. }
  266. data.iSec = data.vals.length - 1;
  267. data.iHour = data.vals.length - 5;
  268. }
  269. data.sec = data.seconds;
  270. $this.data(data);
  271. if(method && methods[method]) {
  272. methods[ method ].call($this, data.seconds);
  273. }
  274. else if(data.start) {
  275. methods.start.call($this, data.seconds);
  276. }
  277. else {
  278. init.call($this, data.seconds);
  279. }
  280. });
  281. };
  282. function init(sec, force) {
  283. var data = this.data(),
  284. $digits = this.find('ul'),
  285. isInterval = false;
  286. if(!data.vals || $digits.length === 0) {
  287. return;
  288. }
  289. if(!sec) {
  290. sec = data.seconds;
  291. }
  292. if(data.intervalId) {
  293. isInterval = true;
  294. clearTimeout(data.intervalId);
  295. }
  296. var days = Math.floor(sec / SECONDS_PER_DAY),
  297. rest = days * SECONDS_PER_DAY,
  298. h = Math.floor((sec - rest) / SECONDS_PER_HOUR);
  299. rest += h * SECONDS_PER_HOUR;
  300. var m = Math.floor((sec - rest) / 60);
  301. rest += m * 60;
  302. var s = sec - rest,
  303. str = (days < 100 ? '0' + (days < 10 ? '0' : '') : '') + days + (h < 10 ? '0' : '') + h + (m < 10 ? '0' : '') + m + (s < 10 ? '0' : '') + s;
  304. for(var i = data.vals.length - 1, j = str.length - 1, v; i >= 0; i--, j--) {
  305. v = parseInt(str.substr(j, 1));
  306. data.vals[i] = v;
  307. $digits.eq(i).children().html(v);
  308. }
  309. if(isInterval || force) {
  310. var me = this;
  311. data.ttStartTime = $.now();
  312. data.intervalId = setTimeout(function() { tick.call(me); }, 1000);
  313. this.data('intervalId', data.intervalId);
  314. }
  315. }
  316. /**
  317. * Switch specified digit by digit index
  318. * @param {number} - digit index
  319. */
  320. function tick(digit) {
  321. var $digits = this.find('ul'),
  322. data = this.data();
  323. if(!data.vals || $digits.length == 0) {
  324. if(data.intervalId) {
  325. clearTimeout(data.intervalId);
  326. this.data('intervalId', null);
  327. }
  328. if(data.callback) {
  329. data.callback();
  330. }
  331. return;
  332. }
  333. if(digit == undefined) {
  334. digit = data.iSec;
  335. }
  336. var n = data.vals[digit],
  337. $ul = $digits.eq(digit),
  338. $li = $ul.children(),
  339. step = data.countdown ? -1 : 1;
  340. $li.eq(1).html(n);
  341. n += step;
  342. if(digit == data.iSec) {
  343. var tickTimeout = data.tickTimeout,
  344. timeDiff = $.now() - data.ttStartTime;
  345. data.sec += step;
  346. tickTimeout += Math.abs(data.seconds - data.sec) * tickTimeout - timeDiff;
  347. data.intervalId = setTimeout(function() { tick.call(me); }, tickTimeout);
  348. }
  349. if(n < 0 || n > data.limits[digit]) {
  350. if(n < 0) {
  351. n = data.limits[digit];
  352. if(digit == data.iHour && data.displayDays > 0 && digit > 0 && data.vals[digit-1] == 0) // fix for hours when day changing
  353. n = 3;
  354. }
  355. else {
  356. n = 0;
  357. }
  358. if(digit > 0) {
  359. tick.call(this, digit-1);
  360. }
  361. }
  362. $li.eq(0).html(n);
  363. var me = this;
  364. if($.support.transition) {
  365. $ul.addClass('transition');
  366. $ul.css({top:0});
  367. setTimeout(function() {
  368. $ul.removeClass('transition');
  369. $li.eq(1).html(n);
  370. $ul.css({top:"-"+ data.height +"px"});
  371. if(step > 0 || digit != data.iSec) {
  372. return;
  373. }
  374. if(data.sec == data.countdownAlertLimit) {
  375. $digits.parent().addClass('timeTo-alert');
  376. }
  377. if(data.sec === 0) {
  378. $digits.parent().removeClass('timeTo-alert');
  379. if(data.intervalId) {
  380. clearTimeout(data.intervalId);
  381. me.data('intervalId', null);
  382. }
  383. if(typeof data.callback === 'function') {
  384. data.callback();
  385. }
  386. }
  387. }, 410);
  388. }
  389. else {
  390. $ul.stop().animate({top:0}, 400, digit != data.iSec ? null : function() {
  391. $li.eq(1).html(n);
  392. $ul.css({top:"-"+ data.height +"px"});
  393. if(step > 0 || digit != data.iSec) {
  394. return;
  395. }
  396. if(data.sec == data.countdownAlertLimit) {
  397. $digits.parent().addClass('timeTo-alert');
  398. }
  399. else if(data.sec == 0) {
  400. $digits.parent().removeClass('timeTo-alert');
  401. if(data.intervalId) {
  402. clearTimeout(data.intervalId);
  403. me.data('intervalId', null);
  404. }
  405. if(typeof data.callback === 'function') {
  406. data.callback();
  407. }
  408. }
  409. });
  410. }
  411. data.vals[digit] = n;
  412. }
  413. return jQuery;
  414. }));