使

使用 HTML、CSS 和 Javascript 构建番茄钟计时器

2025-05-26

使用 HTML、CSS 和 Javascript 构建番茄钟计时器

在本教程中,我们将编写一个番茄钟计时器。⏲

我偶然发现了由 Amy Dutton 和 James Q Quick 为这个假期创建的 Advent Of CSS 和 Advent of JS 挑战。我决定今年参加这个小挑战,会很有趣!

以下是我在第一天挑战中所学到的知识和面临的挑战。😥

什么是番茄工作法计时器?

番茄工作法是由弗朗西斯科·西里洛于20世纪80年代末发明的一种时间管理方法。它使用一个计时器将工作时间划分成几个时间段,通常每25分钟,中间穿插一些短暂的休息。每个时间段被称为一个“番茄”,源于意大利语“番茄”,以西里洛大学时期使用过的番茄形厨房计时器命名。——维基百科

什么是番茄工作法

简单来说,番茄钟是一款简单的应用程序,可以帮助我们集中注意力,提高效率。它可以安排交替的工作和休息时间。

挑战规格

用户应该能够:

  • 单击“开始”链接/按钮启动计时器。
  • 一旦用户点击“开始”,“开始”字样将变为“停止”。然后,用户可以点击“停止”按钮来停止计时器。
  • 单击齿轮图标可更改计时器的长度(分钟和秒)。
  • 一旦计时器结束,环就会从红色变为绿色。
  • 可以使用任何框架、库、工具,或者可以保留旧的 CSS 和 Vanilla JS。

设计规范
我决定继续使用我的老朋友,纯 CSS 和 Vanilla JS 🤞🏻

那么,现在是时候编写一些代码了!

方法:HTML

我们将首先创建一个简单的 HTML 结构来显示计时器和开始/停止以及设置按钮(用于调整时间)

<div class="container">
    <div class="outerRing">
        <div class="timer">
            <!-- Timer elements -->
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Acontainer包含计时器的所有内容。

在 里面container,我们有两个div

一个用于outerRing显示进度条。

第二个用于timer显示倒计时、开始/停止和设置按钮

<div id="time">
    <span id="minutes">00</span>
    <span id="colon">:</span>
    <span id="seconds">10</span>
</div>
<div id="stsp">START</div>
<span id="setting"><i class="fas fa-cog"></i></span>
Enter fullscreen mode Exit fullscreen mode

divtime显示倒计时,带有minutesseconds <span>

以下是完整的 HTML 代码。

<div class="container">
    <div class="outerRing">
        <div class="timer">
            <div id="time">
                <span id="minutes">00</span>
                <span id="colon">:</span>
                <span id="seconds">10</span>
            </div>
            <div id="stsp">START</div>
            <span id="setting"><i class="fas fa-cog"></i></span>
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

方法:添加 CSS

首先,设置: root变量。然后container使用 将布局添加到页面的中心display: grid

将外圈与计时器设置为相差容器15px之间的圆圈。outerRingtimer

.outerRing {
    display: grid;
    place-items: center;
    width: 415px;
    height: 415px;
    border-radius: 50%;
    box-shadow: -5px 14px 44px #000000, 
      5px -16px 50px rgba(255, 255, 255, 0.15);
    background: var(--normal-ring);
}

/* Width and Height difference btwn .outerRing & .timer is 15px, 
where our progress bar will be displayed */

.timer {
    width: 400px;
    height: 400px;
    border-radius: 50%;
    background: var(--timer-bg);
    box-shadow: inset 0px 0px 114px rgba(0, 0, 0, 0.45);
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    padding: 8rem;
}
Enter fullscreen mode Exit fullscreen mode

outerRing是我们将使用该conic-gradient()函数显示进度条的地方。

圆锥渐变如何通过动画实现

Codepen 链接

我们将使用conic-gradient()Javascript 中的颜色来为进度条制作动画。

以下是完整的 CSS 代码。

@import url("https://fonts.googleapis.com/css2?
family=Bebas+Neue&family=Montserrat:wght@700&display=swap");

*,
*::before,
*::after {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

:root {
    --bg: #2b2a30;
    --normal-ring: #17171a;
    --red-ring: #9d0000;
    --green-ring: #00aa51;
    --timer-bg: radial-gradient(
        71.4% 71.4% at 51.7% 28.6%,
        #3a393f 0%,
        #17171a 100%
    );
    --font-timer: "Bebas Neue", cursive;
    --font-stsp: "Montserrat", sans-serif;
    --font-clr: #ffffff;
}

body {
    background: var(--bg);
    min-height: 100vh;
    overflow: hidden;
}

.container {
    height: 600px;
    width: 600px;
    background-color: transparent;
    position: absolute;
    transform: translate(-50%, -50%);
    top: 50%;
    left: 50%;
    display: grid;
    place-items: center;
}

.outerRing {
    display: grid;
    place-items: center;
    width: 415px;
    height: 415px;
    border-radius: 50%;
    box-shadow: -5px 14px 44px #000000, 
        5px -16px 50px rgba(255, 255, 255, 0.15);
    background: var(--normal-ring);
}

.timer {
    width: 400px;
    height: 400px;
    border-radius: 50%;
    background: var(--timer-bg);
    box-shadow: inset 0px 0px 114px rgba(0, 0, 0, 0.45);
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    padding: 8rem;
}

#time {
    width: 300px;
    text-align: center;
    margin: 3rem 0 0 0;
}

#time span {
    display: inline;
    color: var(--font-clr);
    font-family: var(--font-timer);
    font-size: 7rem;
    letter-spacing: 0.1em;
    text-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
}

#stsp {
    color: var(--font-clr);
    cursor: pointer;
    font-family: Montserrat;
    font-weight: bold;
    font-size: 1rem;
    line-height: 1.25rem;
    text-align: center;
    letter-spacing: 0.6em;
    margin: 1rem 0;
    text-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
}

#setting {
    cursor: pointer;
    margin-top: 1rem;
    width: 25px;
    height: 25px;
    color: #585858;
    box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.25);
}
Enter fullscreen mode Exit fullscreen mode

方法:添加 Javascript

首先,让我们完成计时器的组成部分,如计时器显示、启动/停止按钮和设置按钮。

  • 设置按钮

获取SettingMinutesSeconds元素。同时,声明一个toggleSettings变量来跟踪Settings按钮的点击。

const minElem = document.querySelector("#minutes"),
    secElem = document.querySelector("#seconds"),
    setting = document.querySelector("#setting");

let toggleSettings = false;
Enter fullscreen mode Exit fullscreen mode

处理按钮click上的事件Settings。此外,还要处理和元素onblur的事件MinutesSeconds

setting.onclick = function () {
    if (!toggleSettings) {
        toggleSettings = true;
        minElem.contentEditable = true;
        minElem.style.borderBottom = `1px dashed #ffffff50`;
        secElem.contentEditable = true;
        secElem.style.borderBottom = `1px dashed #ffffff50`;
    } else {
        resetValues();
    }
};

minElem.onblur = function () {
    resetValues();
};

secElem.onblur = function () {
    resetValues();
};

Enter fullscreen mode Exit fullscreen mode

该函数处理为resetValues重新分配的值minutesseconds

  • 开始/停止按钮

minutes和声明secondslet变量,因为我们将操作它们来显示计时器。

const startStop = document.querySelector("#stsp");
let minutes = document.querySelector("#minutes").innerHTML,
    seconds = document.querySelector("#seconds").innerHTML;
Enter fullscreen mode Exit fullscreen mode

当我们点击START按钮时,首先会检查minutesseconds不等于0。然后文本将变为STOP并调用该startStopProgress函数。

startStopProgress函数将检查计时器进度并更新进度条和计时器显示。

如果是STOP按钮,使用相同的功能清除进度并将文本改回START

startStop.onclick = function () {
    if (startStop.innerHTML === "START") {
        if (!(parseInt(minutes) === 0 && parseInt(seconds) === 0)) {
            startStop.innerHTML = "STOP";
            startStopProgress();
        } else {
            alert("Enter the Time Value in your Timer!");
        }
    } else {
        startStop.innerHTML = "START";
        startStopProgress();
    }
};
Enter fullscreen mode Exit fullscreen mode
  • 进度条

我们将使用它setInterval()来运行有助于跟踪进度的代码。

function startStopProgress() {
    if (!progress) {
        progress = setInterval(progressTrack, speed);
    } else {
        clearInterval(progress);
        progress = null;
        progressStart = 0;
        progressBar.style.background = `conic-gradient(
                #17171a 360deg,
                #17171a 360deg
          )`;
    }
}
Enter fullscreen mode Exit fullscreen mode

计算剩余分钟数和剩余秒数来更新计时器。

另外,根据计时器的总时间,计算计时器上的度/秒。

Degree/Second = 360 / Total time of the timer in minutes.
Enter fullscreen mode Exit fullscreen mode

使用conic-gradient()和计算出的度/秒,更新 DOM。

function progressTrack() {
    progressStart++;

    secRem = Math.floor((progressEnd - progressStart) % 60);
    minRem = Math.floor((progressEnd - progressStart) / 60);

    secElem.innerHTML = secRem.toString().length == 2 ? secRem : `0${secRem}`;
    minElem.innerHTML = minRem.toString().length == 2 ? minRem : `0${minRem}`;

    progressBar.style.background = `conic-gradient(
        #9d0000 ${progressStart * degTravel}deg,
        #17171a ${progressStart * degTravel}deg
        )`;
    if (progressStart == progressEnd) {
        progressBar.style.background = `conic-gradient(
                #00aa51 360deg,
                #00aa51 360deg
          )`;
        clearInterval(progress);
        startStop.innerHTML = "START";
        progress = null;
        progressStart = 0;
    }
}
Enter fullscreen mode Exit fullscreen mode

这是完整的 Javascript 代码,

const progressBar = document.querySelector(".outerRing"),
    minElem = document.querySelector("#minutes"),
    secElem = document.querySelector("#seconds"),
    startStop = document.querySelector("#stsp"),
    setting = document.querySelector("#setting");

let minutes = document.querySelector("#minutes").innerHTML,
    seconds = document.querySelector("#seconds").innerHTML,
    progress = null,
    progressStart = 0,
    progressEnd = parseInt(minutes) * 60 + parseInt(seconds),
    speed = 1000,
    degTravel = 360 / progressEnd,
    toggleSettings = false,
    secRem = 0,
    minRem = 0;

function progressTrack() {
    progressStart++;

    secRem = Math.floor((progressEnd - progressStart) % 60);
    minRem = Math.floor((progressEnd - progressStart) / 60);

    secElem.innerHTML = secRem.toString().length == 2 ? secRem : `0${secRem}`;
    minElem.innerHTML = minRem.toString().length == 2 ? minRem : `0${minRem}`;

    progressBar.style.background = `conic-gradient(
        #9d0000 ${progressStart * degTravel}deg,
        #17171a ${progressStart * degTravel}deg
        )`;
    if (progressStart == progressEnd) {
        progressBar.style.background = `conic-gradient(
                #00aa51 360deg,
                #00aa51 360deg
          )`;
        clearInterval(progress);
        startStop.innerHTML = "START";
        progress = null;
        progressStart = 0;
    }
}

function startStopProgress() {
    if (!progress) {
        progress = setInterval(progressTrack, speed);
    } else {
        clearInterval(progress);
        progress = null;
        progressStart = 0;
        progressBar.style.background = `conic-gradient(
                #17171a 360deg,
                #17171a 360deg
          )`;
    }
}

function resetValues() {
    if (progress) {
        clearInterval(progress);
    }
    minutes = document.querySelector("#minutes").innerHTML;
    seconds = document.querySelector("#seconds").innerHTML;
    toggleSettings = false;
    minElem.contentEditable = false;
    minElem.style.borderBottom = `none`;
    secElem.contentEditable = false;
    secElem.style.borderBottom = `none`;
    progress = null;
    progressStart = 0;
    progressEnd = parseInt(minutes) * 60 + parseInt(seconds);
    degTravel = 360 / progressEnd;
    progressBar.style.background = `conic-gradient(
                #17171a 360deg,
                #17171a 360deg
          )`;
}

startStop.onclick = function () {
    if (startStop.innerHTML === "START") {
        if (!(parseInt(minutes) === 0 && parseInt(seconds) === 0)) {
            startStop.innerHTML = "STOP";
            startStopProgress();
        } else {
            alert("Enter the Time Value in your Timer!");
        }
    } else {
        startStop.innerHTML = "START";
        startStopProgress();
    }
};

setting.onclick = function () {
    if (!toggleSettings) {
        toggleSettings = true;
        minElem.contentEditable = true;
        minElem.style.borderBottom = `1px dashed #ffffff50`;
        secElem.contentEditable = true;
        secElem.style.borderBottom = `1px dashed #ffffff50`;
    } else {
        resetValues();
    }
};

minElem.onblur = function () {
    resetValues();
};

secElem.onblur = function () {
    resetValues();
};
Enter fullscreen mode Exit fullscreen mode

哇,就是它!🤩🤩

结论!

我们已经成功使用 HTML、CSS 和 Javascript 创建了番茄工作法计时器。

我们可以扩展它来添加更多功能,例如“暂停”按钮等,

如果您有任何问题,请参阅下面的完整代码,

Codepen 链接

欲查看更多此类文章,请访问The Introvert Coder并在Twitter上关注我。

感谢您的阅读,祝您编码愉快!

文章来源:https://dev.to/sansk/build-a-pomodoro-timer-using-html-css-and-javascript-53do
PREV
我希望早点相信的编码建议
NEXT
使用 react-router 在 React 中进行 JWT 身份验证