IOS系統定時APP


將頁面分為時間顯示部分,控制部分,顯示計次共三個部分。實現的功能有:啟動定時器,計次,停止,復位。

計算:當前顯示的時間 = 當前計次的累積時間 + 已經結束的所有計次的累積時間和;

關於 new Date().getTime() 實現,google准確,Firefox 誤差很大;

涉及到的時間計算,都是用 setInterval實現,沒有用 new Date();

嘗試過setInterval 與 new Date兩者混用,一是誤差很大,二是邏輯不夠強;

經測試在google瀏覽器和IOS原組件的誤差很小(毫秒級別),准確度可靠;Firefox 誤差很大;

 1class Stopwatch {
2    constructor(id) {
3        this.container = document.getElementById(id);
4        this.display = this.container.querySelector('.display');   // 時間顯示
5        this.lap = this.container.querySelector('.lap');           // 計次顯示
6
7        // 計數相關變量
8        this._stopwathchTimer = null;                              // 計時器
9        this._count = 0;                                           // 計次的次數
10        this._timeAccumulation = 0;                                // 累積時長
11        this._timeAccumulationContainer = [];                      // 存放已經結束的計次的容器
12        this._s = 0;                                               // 已經結束的所有計次累積時間
13        this._stopwatchHandlers = [];                              // 用於tartTimer里回調的函數
14
15        // 控制流
16        this.ctrl = this.container.querySelector('.ctrl');         // 控制部分
17        if(this.ctrl) {
18            let btns = this.ctrl.querySelectorAll('button');
19            let startStopBtn = btns[1];                            // 開始和暫停按鈕
20            let lapResetBtn = btns[0];                             // 計次和復位按鈕
21
22            // 樣式更改
23            let changeStyle = {                                   
24                clickStart : function(){
25                    lapResetBtn.disabled = '';                     // 計次按鈕生效
26                    startStopBtn.innerHTML = '停止';
27                    startStopBtn.className = 'stop';
28                    lapResetBtn.innerHTML = '計次';
29                    lapResetBtn.className = 'active';
30                },
31                clickStop : function() {
32                    startStopBtn.innerHTML = '啟動';
33                    startStopBtn.className = 'start';
34                    lapResetBtn.innerHTML = '復位';
35                },
36                clickReset : function() {
37                    lapResetBtn.disabled = 'disabled';             // 計次按鈕失效
38                    lapResetBtn.innerHTML = '計次';
39                    lapResetBtn.className = '';
40                    this.display.innerHTML = '00:00.00';
41                    this.lap.innerHTML = ''
42                }
43            };
44
45            // 事件處理函數
46            let eventHandler = {
47                start: function() {
48                    lapResetBtn.removeEventListener('click', resetBind);            // 移除復位事件;選擇啟動,就移除復位
49                    console.log('啟動');
50                    changeStyle.clickStart.call(this);                              // 改變按鈕顯示樣式
51                    if(this._count === 0) {                                         // 如果首次啟動計時器,增加一條計次    
52                        this._count = 1
53                        // console.log('開始事件中的計數次', this._count)
54                        this.insertLap();                                           // 插入計次 
55                    }       
56                    this.startTimer();   
57                    startStopBtn.removeEventListener ('click', startBind);          // 移除啟動計時事件
58                    lapResetBtn.addEventListener('click', lapfBind)                 // 添加計次事件                                                   
59                    startStopBtn.addEventListener('click', stopBind)                // 添加停止計時事件
60                },
61
62                stop: function() {
63                    console.log('停止'); 
64                    changeStyle.clickStop.call(this);                               // 改變按鈕顯示樣式
65                    this.stopTimer();                                               // 停止計時;
66                    startStopBtn.removeEventListener('click', stopBind)             // 移除停止計時事件
67                    startStopBtn.addEventListener('click', startBind);              // 重新添加啟動計時事件
68                    lapResetBtn.removeEventListener('click', lapfBind);             // 移除計次事件;
69                    lapResetBtn.addEventListener('click', resetBind);               // 添加復位事件
70                },
71
72                lapf: function() {                                                       
73                    this.insertLap();                                               // 插入新計次
74                    this._timeAccumulationContainer.push(this._timeAccumulation);   // 將當前結束的計次推入容器,保存起來
75                    this._s += this._timeAccumulationContainer[this._count - 1];    // 累加已經結束的所有計次
76                    console.log('計次''當前累積的計次時間'this._s);
77                    this._timeAccumulation = 0;                                     // 計時器清零,這條放在求和后面!
78                    this._count++;                                            
79                },
80
81                reset: function() {                                                 // 復位事件
82                    console.log('復位');
83                    changeStyle.clickReset.call(this);                              // 改變按鈕顯示
84                    // 重置
85                    this._stopwathchTimer = null;                            
86                    this._count = 0;                                          
87                    this._timeAccumulation = 0;                              
88                    this._timeAccumulationContainer = [];                     
89                    this._s = 0
90                    lapResetBtn.removeEventListener('click', resetBind);            // 復位是所有事件中最后綁定的用完應該刪除
91                }
92            }
93
94            // 事件綁定
95            // 事件函數副本
96            let startBind = eventHandler.start.bind(this),                          // bind 每次會弄出新函數...
97                stopBind = eventHandler.stop.bind(this),                     
98                lapfBind = eventHandler.lapf.bind(this),
99                resetBind = eventHandler.reset.bind(this);
100            startStopBtn.addEventListener('click', startBind);
101        }
102
103        // 用於監聽startTimer
104        this.addStopwatchListener(_timeAccumulation => {
105            this.displayTotalTime(_timeAccumulation);
106        })
107        this.addStopwatchListener(_timeAccumulation => {
108            this.displayLapTime(_timeAccumulation);
109        })
110    }
111
112    // API
113    // 計時器
114    startTimer() {
115        this.stopTimer();
116        this._stopwathchTimer = setInterval(() => {   
117            this._timeAccumulation++;                          // 注意時間累積量 _timeAccumulation 是厘秒級別的(因為界面顯示的是兩位)
118            this._stopwatchHandlers.forEach(handler => {       // 處理回調函數
119                handler(this._timeAccumulation);
120            })
121        }, 1000 / 100)
122    }
123
124    stopTimer() {
125        clearInterval(this._stopwathchTimer );
126    }
127
128    // 總時間顯示(從啟動到當前時刻的累積時間)
129    displayTotalTime(_timeAccumulation) {
130        let totaltimeAccumulation = this._timeAccumulation * 10  + this._s * 10;     // _s為_timeAccumulation累積時間隊列之和;
131        this.display.innerHTML = `${this.milSecond_to_time(totaltimeAccumulation)}`;
132    }
133    // 計次條目顯示
134    displayLapTime(_timeAccumulation) {
135        let li = this.lap.querySelector('li'),
136            spans = li.querySelectorAll('span'),
137            task = spans[0], time = spans[1];
138
139        task.innerHTML = `計次${this._count}`;
140        time.innerHTML = `${this.milSecond_to_time(this._timeAccumulation * 10)}`;
141    }
142
143    // 插入一個計次
144    insertLap() {
145        let t = this.templateLap(); // 顯示計次
146        this.lap.insertAdjacentHTML('afterBegin', t);
147    }
148    // 計次內容模板
149    templateLap() {
150        let t = `
151        <li><span></span><span></span></li>
152        `
153        return t;
154    }
155
156    // 將時間累積量轉化成時間
157    milSecond_to_time(t) {                                         // t 時間間隔,單位 ms
158        let time,
159            minute = this.addZero(Math.floor(t / 60000) % 60),     // 分
160            second = this.addZero(Math.floor(t / 1000) % 60),      // 秒
161            centisecond = this.addZero(Math.floor(t / 10) % 100) ; // 厘秒(百分之一秒)
162        time = `${minute}:${second}.${centisecond}`;
163        return time;
164    }
165    // 修飾器;加零
166    addZero(t) {
167        t = t < 10 ? '0' + t : t; 
168        return t;
169    }
170    // 添加監聽startTimer的事件函數
171    addStopwatchListener(handler) {
172        this._stopwatchHandlers.push(handler);
173    }
174}
175
176// 調用
177const stopwatch = new Stopwatch('stopwatch');

一個200行的小demo,收獲不少

從基於實現組件功能開始,到使用class封裝組件;

最小化訪問DOM元素;

相關變量放在一起,將樣式更改函數放在一塊,將事件處理函數放在一塊;

綁定this(非箭頭函數this丟失),bind的時候每次都會重新生成新函數(將函數bind后統一賦給一個變量,這樣增加事件和刪除事件所用的函數就是同一個了);

增加事件監聽器,統一管理需要調用函數變量的系列相關事件;

將函數抽象到最純(函數就是函數不與組件的元素相互耦合),使用Decorate(裝飾器);

由於在同一個按鈕上綁定了不同的事件,因此事件綁定與移除的順序很重要;

https://rencoo.github.io/appDemo/iosStopwatch/index.html
另外一篇文章, 用狀態模式重構了這個小demo https://www.cnblogs.com/rencoo/p/10115341.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM