// 将页面分为时间显示部分,控制部分,显示计次共三个部分。 // 实现的功能有:启动定时器,计次,停止,复位。 // 计算 :当前显示的时间 = 当前计次的累积时间 + 已经结束的所有计次的累积时间和; // 关于 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,收获不少.
项目已经放到 GitHub上了,欢迎大佬拍砖 https://rencoo.github.io/appDemo/stopwatch/index.html