Skip to main content
如何用JavaScript实现秒表计时器

如何用JavaScript实现秒表计时器

· 0 · 0 ·
javascript
小绵尾巴
Author
小绵尾巴
正在路上的前端工程师
Table of Contents

在这篇文章中会展示如何用JavaScript实现一个秒表计时器,这是我在学习JavaScript过程中的一个练习项目,接下来的内容也只是我作为初学者在练习过程中的理解。

在一个秒表计时器(stop watch)中会有开始计时暂停计时,与重置时间的功能,同时,我们希望在暂停计时后,还能从当前位置继续计时,接下来将实现这些功能。

Prerequisite #

这篇博文假设你已经学过JavaScript中的:

  • Date Object
  • DOM
  • Arrow Function(箭头函数)
  • Callback (回调函数)
  • addEventListener()
  • setInterval() & setTimeout()
  • Math.floor()

Date.now() #

在开始写代码前,先了解Date Object中的Date.now()方法。

Date.now()是写在JavaScript内嵌对象Date中的方法,表示从纪元时间(UTC 1970/01/01)到现在经过的时间,返回以毫秒为单位的值。

比如在写这篇文章的当下,Date.now()返回的值是:1677832655179,意味着从1970/01/01到现在一共流逝了1677832655179毫秒。

下面是一个简单的例子:

    const startTime = Date.now();
    console.log("开始计时");

    setTimeout(() => { 
        const elapsedTime = Date.now() - startTime;
        elapsedTime = Math.floor(elapsedTime / 1000);
        console.log(`结束计时,用时: ${elapsedTime}`s, 2000)
    })

    //Expected output: 结束计时,用时:2s
  • 我们把【打印「开始计时」的时间】赋值给变量startTime,然后打印出“开始计时”。
  • setTimeout()方法设置2秒后打印「结束计时」语句,同时打印出两条打印语句的间隔时间elapsedTime
  • 间隔时间elapsedTime等于【当前打印「结束计时」时的时间 Date.now()】减去【打印「开始计时」时的时间 startTime
  • 间隔时间elapsedTime应该为2s,因为setTimeout()设置的时间是2s

理解了这个例子之后,我们会用这个·Date.now()方法表示计时器中涉及的时间。

HTML #

我们从HTML开始,写出计时器中基本的文本与按钮。

<div class="container">
    <h1 id="timeDisplay">00:00:00:00</h1>
    <button type="button" id="startBtn">Start</button>
    <button type="button" id="pauseBtn">Pause</button>
    <button type="button" id="resetBtn">Reset</button>
</div>

使用了一些非常基础的HTML标签:

  • 用一级标题<h1>来显示页面上的时间:00:00:00:00
  • 用三个按钮<button>分别用于start(开始计时),pause(暂停计时),reset(重置时间)。

目前我们不关心CSS样式,只关心计时器的实现,你可以按照自己的喜好设计计时器,接下来用JavaScript实现计时器的功能。

JavaScript #

首先,声明我们需要的变量:

const timeDisplay = document.querySelector("#timeDisplay");
const startBtn = document.querySelector("#startBtn");
const pauseBtn = document.querySelector("#pauseBtn");
const resetBtn = document.querySelector("#resetBtn");

let startTime = 0;
let elapsedTime = 0;
let paused = true;
let intervallId;
let hours = 0;
let minutes = 0;
let seconds = 0;
let miliseconds = 0;
  • document.querySelector选择的是4个HTML中的元素(Element),一级标题和三个按钮。
  • startTimeelapsedTime分别表示开始时间间隔时间
    • 开始时间startTime有两种,一种是从 00:00:00:00 开始计时的时间,另一种是暂停后继续计时的开始时间。
    • 间隔时间elapsedTime也有两种,一种是setInterval()设置的间隔时间,另一种是暂停后继续计时中暂停了多久的间隔时间。
  • paused是一个布尔值,用于判断当前程序是暂停了,还是正在运行。在一开始时,把paused设置成false
  • intervalIdsetInterval()这个timer的id,一开始只声明不赋值。
  • hours, minutes, seconds, miliseconds分别代表 00:00:00:00 中各自的单位。

The start button #

然后我们实现开始计时的功能:

startBtn.addEventListener("click", () => {
    if(paused){
            paused = false;
            startTime = Date.now() - elapsedTime;
            intervalId = setInterval(updateTime, 75);
    }
})
  • 给开始计时的按钮startBtn添加点击事件,设置点击开始计时的按钮后会执行的程序。
  • 检查暂停状态paused是否为true,如果程序是暂停的,则将暂停状态paused设置成false,表示计时器是开启的状态。
  • 开始计时的时间startTime会等于现在的时间Date.now()减去间隔时间elapsedTime
  • 因为接下来会用setInterval设置一个重复定时调用的函数,比如每隔1000毫秒调用一次函数,此时的1000毫秒就是elapsedTime
    • 最开始的elapsedTime我们在声明变量时已经设置成0;
  • setInterval()设置每隔75毫秒调用一次updateTime()函数。

The updateTime function #

接下来定义updateTime()函数:

  1. 我们先看最基础的逻辑,updateTime()到底要实现什么:

    function updateTime(){
        elapsedTime = Date.now() - startTime;
    
        miliseconds = Math.floor(elapsedTime % 1000);
        seconds = Math.floor(elapsedTime / 1000 % 60);
        minutes = Math.floor(elapsedTime / 1000 / 60 % 60);
        hours = Math.floor(elapsedTime / 1000 / 60 / 60 % 60);
    
        timeDisplay.textContent = `${hours}:${minutes}:${seconds}:${miliseconds}`;
    }
    
    • 间隔时间elapsedTime等于现在的时间Date.now()减去开始计时的时间startTime。而这个间隔时间就是我们显示在页面上的时间。
      • 举个例子,我从8:30开始计时,现在的时间是8:35,一共过去了5分钟,那么现在计时器上的时间就应该是5分钟。
    • 由于Date.now()返回的是以毫秒为单位的值,那么计算出的间隔时间elapsedTime的单位也是毫秒数,我们要把它格式化成00:00:00:00的形式
    • 由于1000毫秒等于1秒,那么每过1000毫秒,秒数就会增加1秒,而毫秒位会归零重新计数,所以用变量miliseconds等于elapsedTime取余%1000的值。秒数,分钟数,时钟数也是这个思路去格式化。
    • 将格式化后的elapsedTime显示在HTML页面上。但是此时会有一个问题:在显示时,秒数、分钟数、时钟数有1位数和2位数的情况,我们希望当它们是1位数时,前面会有1个0来组成2位数。
  2. 我们在updateTime()函数中写一个内嵌函数formatTime(),当秒数、分钟数、时钟数是1位数时,在前面添加一个0:

    function formatTime(unit){
        return (("0") + unit).length > 2 ? unit : "0" + unit;
    }
    
    • formatTime()函数接受一个时间单位作为参数,
    • 判断当"0"加上一个时间单位时,这个时间单位的字符串长度是否大于2。
      • 如果"0" + unit的字符串长度大于2,说明这是一个2位数的时间单位,只需要返回这个时间单位;
      • 如果"0" + unit的字符串小于2,说明这是一个1位数的时间单位,返回"0" + unit
  3. formateTime()我们没有考虑miliseconds的情况,因为它最多可以有3位数,我们用一个formatMili()来单独实现毫秒的格式化:

    function formatMili(unit){
        unit = String(unit);
    
        switch(unit.length){
            case 1: 
                return "0" + unit;
                break;
            case 2:
                return unit;
                break;
            case 3:
                return unit.slice(0, 2);
                break;
        }
    }
    
    • 首先将数字类型的miliseconds类型转化为字符串类型,这样才能用String.length来判断毫秒数是几位数。
    • 用switch条件语句分别判断当毫秒数是1位数、2位数、3位数时的情况。
  4. 完整的updateTime()函数的代码是:

     function updateTime(){
        elapsedTime = Date.now() - startTime;
    
        miliseconds = Math.floor(elapsedTime % 1000);
        seconds = Math.floor(elapsedTime / 1000 % 60);
        minutes = Math.floor(elapsedTime / 1000 / 60 % 60);
        hours = Math.floor(elapsedTime / 1000 / 60 / 60 % 60);
    
        miliseconds = formatMili(miliseconds);
        seconds = formatTime(seconds);
        minutes = formatTime(minutes);
        hours = formatTime(hours);
    
        timeDisplay.textContent = `${hours}:${minutes}:${seconds}`;
    
        function formatTime(unit){
            return (("0") + unit).length > 2 ? unit : "0" + unit;
        }
        function formatMili(unit){
            unit = String(unit);
            switch(unit.length){
                case 1: 
                    return "0" + unit;
                    break;
                case 2:
                    return unit;
                    break;
                case 3:
                    return unit.slice(0, 2);
                    break;
            }
        }
    }
    

The pause button #

pauseBtn.addEventListener("click", () => {
    if(!paused){
        paused = true;
        elapsedTime = Date.now() - startTime;
        clearInterval(intervalId);
    }
})
  • 给暂停计时的按钮添加点击事件,设置点击暂停计时的按钮后会执行的程序。
  • 检查现在是否不是处于暂停状态,即是否是正在计时的状态,如果是,则将paused设置成true,表示现在是暂停状态。
  • 计算暂停了多长时间eplasedTime
  • 调用clearInterval终止setInterval()设置的重复定时任务,即停止更新页面上的时间。

The reset button #

重置时间是一个比较简单的功能,重置就是把所有状态恢复到最开始的状态。

resetBtn.addEventListener("click", () => {
    paused = true;
    clearInterval(intervalId);
    startTime = 0;
    elapsedTime = 0;
    hours = 0;
    minutes = 0;
    seconds = 0;
    miliseconds = 0;

    timeDisplay.textContent = "00:00:00:00";
})
  • 把暂停状态paused设置成true,因为现在没有在计时。
  • 调用clearInterval终止setInterval()设置的重复定时任务,即停止更新页面上的时间。
  • 把所有变量重新设置为0
  • 将HTML的文本重新设置为00:00:00:00

到此为止,一个秒表计时器的功能已经全部实现。

结语 #

这个project我在学习的时候有很多地方卡壳想不明白,写完这篇博文后发现思路其实非常清晰,看来「The best way of learning is teaching.」is so damn true

Reference #




评论