/** * countdown.js. * @author hgwang * @date 2016-05-29 */ 'use strict'; var $ = require('yoho-jquery'); var tip = require('../../plugin/tip'); var EVENT_AFTER_PAINT = 'afterPaint'; var defaultOPtions = { el: {}, // unix时间戳,单位应该是毫秒! stopPoint: 0, leftTime: 0, template: '', // '${h}时${m}分${s-ext}秒' varRegular: /\$\{([\-\w]+)\}/g, clock: ['d', 100, 2, 'h', 24, 2, 'm', 60, 2, 's', 60, 2, 'u', 10, 1], effect: 'normal' }; var effect = { normal: { paint: function() { var me = this, content; // 找到值发生改变的hand $.each(me.hands, function(index, hand) { if (hand.lastValue !== hand.value) { // 生成新的markup content = ''; $.each(me._toDigitals(hand.value, hand.bits), function(i, digital) { content += me._html(digital, '', 'digital'); }); // 并更新 hand.node.html(content); } }); } } }; var timer = (function() { var fns = [], commands = [];// 操作指令 /** * timer * 调用频率为100ms一次。努力精确计时,调用帧函数 */ function timerIn() { // 计算新时间,调整diff var diff = +new Date() - timerIn.nextTime, count = 1 + Math.floor(diff / 100); // 循环处理fns二元组 var frequency, step, i, len; // 为避免循环时受到 对fns数组操作 的影响, // add/remove指令提前统一处理 while (commands.length) { commands.shift()(); } diff = 100 - diff % 100; timerIn.nextTime += 100 * count; for (i = 0, len = fns.length; i < len; i += 2) { frequency = fns[i + 1]; // 100次/s的 if (frequency === 0) { fns[i](count); // 1000次/s的 } else { // 先把末位至0,再每次加2 frequency += 2 * count - 1; step = Math.floor(frequency / 20); if (step > 0) { fns[i](step); } // 把末位还原成1 fns[i + 1] = frequency % 20 + 1; } } // next setTimeout(timerIn, diff); } // 首次调用 timerIn.nextTime = +new Date(); timerIn(); function indexOf(item, arr) { var i, len; for (i = 0, len = arr.length; i < len; ++i) { if (arr[i] === item) { return i; } } return -1; } return { add: function(fn, frequency) { commands.push(function() { fns.push(fn); fns.push(frequency === 1000 ? 1 : 0); }); }, remove: function(fn) { var i; commands.push(function() { i = indexOf(fn, fns); if (i !== -1) { fns.splice(indexOf(fn, fns), 2); } }); } }; }()); function Countdown(config) { var cfg; if (!(this instanceof Countdown)) { return new Countdown(config); } config.el = $(config.el); if (!config.el) { return; } cfg = config.el.attr('data-config'); if (cfg) { cfg = JSON.parse(cfg.replace(/'/g, '"')); config = $.extend(true, {}, defaultOPtions, cfg, config); } this.config = config; this._init(); } $.extend(Countdown.prototype, { /** * 初始化 * @private */ _init: function() { var me = this; var el = me.config.el; // 初始化时钟. var hands = []; // 分析markup var tmpl = el.html(); var varRE = me.config.varRegular; var clock; var _reflow; /** * 指针结构 * hand: { * type: string, * value: number, * lastValue: number, * base: number, * radix: number, * bits: number, * node: S.Node * } */ me.hands = hands; me.frequency = 1000; me._notify = []; varRE.lastIndex = 0; el.html(tmpl.replace(varRE, function(str, type) { // 生成hand的markup var content = ''; // 时钟频率校正. if (type === 'u' || type === 's-ext') { me.frequency = 100; } if (type === 's-ext') { hands.push({type: 's'}); hands.push({type: 'u'}); content = me._html('', 's', 'handlet') + me._html('.', '', 'digital') + me._html('', 'u', 'handlet'); } else { hands.push({type: type}); } return me._html(content, type, 'hand'); })); // 指针type以外属性(node, radix, etc.)的初始化. clock = me.config.clock; $.each(hands, function(index, hand) { var type = hand.type, base = 100, i; hand.node = el.find('.hand-' + type); // radix, bits 初始化. for (i = clock.length - 3; i > -1; i -= 3) { if (type === clock[i]) { break; } base *= clock[i + 1]; } hand.base = base; hand.radix = clock[i + 1]; hand.bits = clock[i + 2]; }); me._getLeft(); me._reflow(); // bind reflow to me. _reflow = me._reflow; me._reflow = function() { return _reflow.apply(me, arguments); }; timer.add(me._reflow, me.frequency); // 显示时钟. el.show(); }, /** * 获取倒计时剩余帧数 */ _getLeft: function() { // {{{ var left = this.config.leftTime * 1000; var end = this.config.stopPoint; // 这个是UNIX时间戳,毫秒级 if (!left && end) { left = end - (+new Date()); } this.left = left - left % this.frequency; }, // }}} /** * 更新时钟 */ _reflow: function(count) { var me = this; var el = me.config.el; count = count || 0; me.left = me.left - me.frequency * count; // 更新hands $.each(me.hands, function(index, hand) { hand.lastValue = hand.value; hand.value = Math.floor(me.left / hand.base) % hand.radix; }); // 更新时钟. me._repaint(); // notify if (me._notify[me.left]) { $.each(me._notify[me.left], function(index, callback) { callback.call(me); }); } // notify 可能更新me.left if (me.left < 1) { el.text('— 活动已结束 —'); el.parents('.back-ground-white').on('click', function() { tip.show('活动即将开始,敬请期待!'); return false; }); timer.remove(me._reflow); } if (me.left < 86400000) { el.find('.left-day').hide(); } return me; }, /** * 重绘时钟 * @private */ _repaint: function() { effect[this.config.effect].paint.apply(this); this.config.el.trigger(EVENT_AFTER_PAINT); }, /** * 把值转换为独立的数字形式 * @private * @param {number} value * @param {number} bits */ _toDigitals: function(value, bits) { var digitals = []; value = value < 0 ? 0 : value; value.toString().length > 1 ? bits = 2 : bits = 1; // 把时、分、秒等换算成数字. while (bits--) { digitals[bits] = value % 10; value = Math.floor(value / 10); } return digitals; }, /** * 生成需要的html代码,辅助工具 * @private * @param {string|Array.<string>} content * @param {string} className * @param {string} type */ _html: function(content, className, type) { if ($.isArray(content)) { content = content.join(''); } switch (type) { case 'hand': className = type + ' hand-' + className; break; case 'handlet': className = type + ' hand-' + className; break; case 'digital': if (content === '.') { className = type + ' ' + type + '-point ' + className; } else { className = type + ' ' + type + '-' + content + ' ' + className; } break; default: break; } return '<i class="' + className + '">' + content + '</i>'; }, /** * 倒计时事件 * @param {number} time unit: second * @param {Function} callback */ notify: function(time, callback) { var notifies; time = time * 1000; time = time - time % this.frequency; notifies = this._notify[time] || []; notifies.push(callback); this._notify[time] = notifies; return this; } }); exports.Countdown = Countdown;