一个IOS系统定时APP,功能与系统定时器一致

// 将页面分为时间显示部分,控制部分,显示计次共三个部分。
// 实现的功能有:启动定时器,计次,停止,复位。

// 计算 :当前显示的时间 = 当前计次的累积时间 + 已经结束的所有计次的累积时间和; 
// 关于 new Date().getTime() 实现,google准确,Firefox 误差很大;
// 涉及到的时间计算,都是用 setInterval实现,没有用 new Date();
// 尝试过setInterval 与 new Date两者混用,一是误差很大,二是逻辑不够强;
// 经测试在google浏览器和IOS原组件的误差很小(毫秒级别),准确度可靠;Firefox 误差很大;
class Stopwatch {
    constructor(id) {
        this.container = document.getElementById(id);
        this.display = this.container.querySelector(‘.display‘);   // 时间显示
        this.lap = this.container.querySelector(‘.lap‘);           // 计次显示

        // 计数相关变量
        this._stopwathchTimer = null;                           // 计时器
        this._count = 0;                                        // 计次的次数
        this._timeAccumulation = 0;                             // 累积时长
        this._timeAccumulationContainer = [];                   // 存放已经结束的计次的容器
        this._s = 0;                                            // 已经结束的所有计次累积时间
        this._stopwatchHandlers = [];                           // 用于tartTimer里回调的函数

        // 控制流
        this.ctrl = this.container.querySelector(‘.ctrl‘);       // 控制部分
        if(this.ctrl) {
            this.btns = this.ctrl.querySelectorAll(‘button‘);
            this.startStopBtn = this.btns[1];                        // 开始和暂停按钮
            this.lapResetBtn = this.btns[0];                         // 计次和复位按钮
            this.changeStyle = {                                     // 样式更改
                    clickStart : function(){
                        this.lapResetBtn.disabled = ‘‘;              // 计次按钮生效
                        this.startStopBtn.innerHTML = ‘停止‘;
                        this.startStopBtn.className = ‘stop‘;
                        this.lapResetBtn.innerHTML = ‘计次‘;
                        this.lapResetBtn.className = ‘active‘;
                    },
                    clickStop : function() {
                        this.startStopBtn.innerHTML = ‘启动‘;
                        this.startStopBtn.className = ‘start‘;
                        this.lapResetBtn.innerHTML = ‘复位‘;
                    },
                    clickReset : function() {
                        this.lapResetBtn.disabled = ‘disabled‘;       // 计次按钮失效
                        this.lapResetBtn.innerHTML = ‘计次‘;
                        this.lapResetBtn.className = ‘‘;
                        this.display.innerHTML = ‘00:00.00‘;
                        this.lap.innerHTML = ‘‘; 
                    }
                };
           
            // 事件绑定
            // 事件函数副本
            this.startBind = this.start.bind(this);                   // bind 每次会弄出新函数...
            this.stopBind = this.stop.bind(this);                     
            this.lapfBind = this.lapf.bind(this);
            this.resetBind = this.reset.bind(this);
            this.startStopBtn.addEventListener(‘click‘, this.startBind);
        }

        this.addStopwatchListener(_timeAccumulation => {
            this.displayTotalTime(_timeAccumulation);
        })
        this.addStopwatchListener(_timeAccumulation => {
            this.displayLapTime(_timeAccumulation);
        })
    }

    // API
    // 计时器
    start() {
        this.lapResetBtn.removeEventListener(‘click‘, this.resetBind);             // 移除复位事件;选择启动,就移除复位
        console.log(‘启动‘);
        this.changeStyle.clickStart.call(this);                                    // 改变按钮显示样式
        if(this._count === 0) {                                                    // 如果首次启动计时器,增加一条计次    
            this._count = 1; 
            // console.log(‘开始事件中的计数次‘, this._count)
            this.insertLap();                                                      // 插入计次 
        }       
        this.startTimer();   
        this.startStopBtn.removeEventListener (‘click‘, this.startBind);           // 移除启动计时事件
        this.lapResetBtn.addEventListener(‘click‘, this.lapfBind)                  // 添加计次事件                                                   
        this.startStopBtn.addEventListener(‘click‘, this.stopBind)                 // 添加停止计时事件
    }

   stop() {
        console.log(‘停止‘); 
        this.changeStyle.clickStop.call(this);                                     // 改变按钮显示样式
        this.stopTimer();                                                          // 停止计时;
        this.startStopBtn.removeEventListener(‘click‘, this.stopBind)              // 移除停止计时事件
        this.startStopBtn.addEventListener(‘click‘, this.startBind);               // 重新添加启动计时事件
        this.lapResetBtn.removeEventListener(‘click‘, this.lapfBind);              // 移除计次事件;
        this.lapResetBtn.addEventListener(‘click‘, this.resetBind);                // 添加复位事件
    }  

    lapf() {                                                       
        this.insertLap();                                              // 插入新计次
        this._timeAccumulationContainer.push(this._timeAccumulation);  // 将当前结束的计次推入容器,保存起来
        this._s += this._timeAccumulationContainer[this._count - 1];   // 累加已经结束的所有计次
        console.log(‘计次‘, this._count);
        this._timeAccumulation = 0;                                    // 计时器清零,这条放在求和后面!
        this._count++;                                            
    }

    reset() {                                                          // 复位事件
        console.log(‘复位‘);
        this.changeStyle.clickReset.call(this);                        // 改变按钮显示
        // 重置
        this._stopwathchTimer = null;                            
        this._count = 0;                                          
        this._timeAccumulation = 0;                              
        this._timeAccumulationContainer = [];                     
        this._s = 0; 
        this.lapResetBtn.removeEventListener(‘click‘, this.resetBind);      // 复位是所有事件中最后绑定的用完应该删除
    }

    startTimer() {
        this.stopTimer();
        this._stopwathchTimer = setInterval(() => {   
            this._timeAccumulation++;                                       // 注意时间累积量 _timeAccumulation 是厘秒级别的(由于显示的是两位)
            this._stopwatchHandlers.forEach(handler => {
                handler(this._timeAccumulation);
            })
        }, 1000 / 100)
    }

    stopTimer() {
        clearInterval(this._stopwathchTimer );
    }

    // 总时间显示(从启动到当前时刻的累积时间)
    displayTotalTime(_timeAccumulation) {
        let totaltimeAccumulation = this._timeAccumulation * 10  + this._s * 10;     // _s为_timeAccumulation累积时间队列之和;
        this.display.innerHTML = `${this.milSecond_to_time(totaltimeAccumulation)}`;
    }
    // 计次条目显示
    displayLapTime(_timeAccumulation) {
        let li = this.lap.querySelector(‘li‘),
            spans = li.querySelectorAll(‘span‘),
            task = spans[0], time = spans[1];

        task.innerHTML = `计次${this._count}`;
        time.innerHTML = `${this.milSecond_to_time(this._timeAccumulation * 10)}`;
    }

    // 插入一个计次
    insertLap() {
        let t = this.templateLap(); // 显示计次
        this.lap.insertAdjacentHTML(‘afterBegin‘, t);
    }
    // 计次内容模板
    templateLap() {
        let t = `
        <li><span></span><span></span></li>
        `
        return t;
    }

    // 将时间累积量转化成时间
    milSecond_to_time(t) {                                         // t 时间间隔,单位 ms
        let time,
            minute = this.addZero(Math.floor(t / 60000) % 60),     //
            second = this.addZero(Math.floor(t / 1000) % 60),      //
            centisecond = this.addZero(Math.floor(t / 10) % 100) ; // 厘秒(百分之一秒)
        time = `${minute}:${second}.${centisecond}`;
        return time;
    }
    // 修饰器;加零
    addZero(t) {
        t = t < 10 ? ‘0‘ + t : t; 
        return t;
    }
    // 将函数推入回调队列
    addStopwatchListener(handler) {
        this._stopwatchHandlers.push(handler);
    }
}

// 调用
const stopwatch = new Stopwatch(‘stopwatch‘);

第一个200行的小demo,收获不少.

一个IOS系统定时APP,功能与系统定时器一致一个IOS系统定时APP,功能与系统定时器一致

项目已经放到 GitHub上了,欢迎大佬拍砖 https://rencoo.github.io/appDemo/stopwatch/index.html

 

一个IOS系统定时APP,功能与系统定时器一致

上一篇:iOS开发:setNeedsLayOut和setNeedsDisplay区别


下一篇:Final Cut Pro X 插件-TranSolve-20种撕裂溶解噪波划痕转场效果FCPX插件 https://www.cgaee.com