僅用18行JavaScript實(shí)現(xiàn)一個(gè)倒數(shù)計(jì)時(shí)器
前言
有時(shí),您將需要構(gòu)建一個(gè)JavaScript倒數(shù)時(shí)鐘。您可能有活動(dòng),銷售,促銷或游戲。您可以使用原始JavaScript構(gòu)建時(shí)鐘,而不用尋找最近的插件。雖然有很多很棒的時(shí)鐘插件,但是使用原始JavaScript可以帶來以下好處:
- 您的代碼將是輕量級(jí)的,因?yàn)樗鼘⒕哂辛阋蕾囆浴?/li>
- 您的網(wǎng)站將表現(xiàn)更好。您無需加載外部腳本和樣式表。
- 您將擁有更多控制權(quán)。您將構(gòu)建時(shí)鐘,使其行為完全符合您希望的方式(而不是嘗試將插件彎曲到您的意愿)。
因此,事不宜遲,這里介紹了如何僅用18行JavaScript來制作自己的倒計(jì)時(shí)時(shí)鐘

基本時(shí)鐘:倒數(shù)到特定的日期或時(shí)間
以下是創(chuàng)建基本時(shí)鐘所涉及步驟的快速概述:
- 設(shè)置有效的結(jié)束日期。
- 計(jì)算剩余時(shí)間。
- 將時(shí)間轉(zhuǎn)換為可用格式。
- 將時(shí)鐘數(shù)據(jù)輸出為可重復(fù)使用的對(duì)象。
- 在頁(yè)面上顯示時(shí)鐘,并在時(shí)鐘為零時(shí)停止時(shí)鐘。
設(shè)定有效的結(jié)束日期
首先,您需要設(shè)置一個(gè)有效的結(jié)束日期。這應(yīng)該是JavaScript的Date.parse()方法可以理解的任何格式的字符串。例如:
在ISO 8601格式:
- const deadline = '2015-12-31';
簡(jiǎn)短格式:
- const deadline = '31/12/2015';
或者,長(zhǎng)格式:
- const deadline = 'December 31 2015';
這些格式中的每一種都允許您指定確切的時(shí)間和時(shí)區(qū)(對(duì)于ISO日期,則為UTC的偏移量)。例如:
- const deadline = 'December 31 2015 23:59:59 GMT+0200';
您可以在本文中閱讀有關(guān)JavaScript中日期格式的更多信息。
計(jì)算剩余時(shí)間
下一步是計(jì)算剩余時(shí)間。我們需要編寫一個(gè)函數(shù),該函數(shù)需要一個(gè)表示給定結(jié)束時(shí)間的字符串(如上所述)。然后,我們計(jì)算該時(shí)間與當(dāng)前時(shí)間之間的時(shí)差??雌饋硐襁@樣:
- function getTimeRemaining(endtime){
- const total = Date.parse(endtime) - Date.parse(new Date());
- const seconds = Math.floor( (total/1000) % 60 );
- const minutes = Math.floor( (total/1000/60) % 60 );
- const hours = Math.floor( (total/(1000*60*60)) % 24 );
- const days = Math.floor( total/(1000*60*60*24) );
- return {
- total,
- days,
- hours,
- minutes,
- seconds
- };
- }
首先,我們創(chuàng)建一個(gè)變量total,以保留剩余時(shí)間直到截止日期。該Date.parse()函數(shù)將時(shí)間字符串轉(zhuǎn)換為毫秒值。這使我們可以相減兩次,并獲得兩者之間的時(shí)間量。
- const total = Date.parse(endtime) - Date.parse(new Date());
將時(shí)間轉(zhuǎn)換為可用格式
現(xiàn)在,我們要將毫秒轉(zhuǎn)換為天,小時(shí),分鐘和秒。讓我們以秒為例:
- const seconds = Math.floor( (t/1000) % 60 );
讓我們分解一下這里發(fā)生的事情。
- 將毫秒除以1000可轉(zhuǎn)換為秒: (t/1000)
- 將總秒數(shù)除以60,然后取余數(shù)。您不希望所有的秒數(shù),僅需要計(jì)算分鐘數(shù)之后剩下的秒數(shù):(t/1000) % 60
- 四舍五入到最接近的整數(shù)。這是因?yàn)槟枰暾拿霐?shù),而不是幾分之一秒:Math.floor( (t/1000) % 60 )
重復(fù)此邏輯,將毫秒轉(zhuǎn)換為分鐘,小時(shí)和天。
輸出時(shí)鐘數(shù)據(jù)作為可重用對(duì)象
準(zhǔn)備好幾天,幾小時(shí),幾分鐘和幾秒鐘之后,我們現(xiàn)在可以將數(shù)據(jù)作為可重用的對(duì)象返回:
- return {
- total,
- days,
- hours,
- minutes,
- seconds
- };
該對(duì)象允許您調(diào)用函數(shù)并獲取任何計(jì)算值。這是如何獲取剩余時(shí)間的示例:
- getTimeRemaining(deadline).minutes
方便吧?
顯示時(shí)鐘并在達(dá)到零時(shí)停止
現(xiàn)在,我們有了一個(gè)可以花費(fèi)剩余的天,小時(shí),分鐘和秒的功能,我們可以構(gòu)建時(shí)鐘了。首先,我們將創(chuàng)建以下HTML元素來保存時(shí)鐘:
然后,我們將編寫一個(gè)在新div中輸出時(shí)鐘數(shù)據(jù)的函數(shù):
- function initializeClock(id, endtime) {
- const clock = document.getElementById(id);
- const timeinterval = setInterval(() => {
- const t = getTimeRemaining(endtime);
- clock.innerHTML = 'days: ' + t.days + '<br>' +
- 'hours: '+ t.hours + '<br>' +
- 'minutes: ' + t.minutes + '<br>' +
- 'seconds: ' + t.seconds;
- if (t.total <= 0) {
- clearInterval(timeinterval);
- }
- },1000);
- }
該函數(shù)有兩個(gè)參數(shù)。這些是包含我們時(shí)鐘的元素的ID,以及倒計(jì)時(shí)的結(jié)束時(shí)間。在函數(shù)內(nèi)部,我們將聲明一個(gè)clock變量并將其用于存儲(chǔ)對(duì)我們的時(shí)鐘容器div的引用。這意味著我們不必繼續(xù)查詢DOM。
接下來,我們將使用setInterval每秒執(zhí)行一個(gè)匿名函數(shù)。此功能將執(zhí)行以下操作:
- 計(jì)算剩余時(shí)間。
- 將剩余時(shí)間輸出到我們的div。
- 如果剩余時(shí)間為零,請(qǐng)停止計(jì)時(shí)。
- 此時(shí),剩下的唯一步驟是像這樣運(yùn)行時(shí)鐘:
- initializeClock('clockdiv', deadline);
恭喜你!現(xiàn)在,您僅用18行JavaScript就擁有了一個(gè)基本時(shí)鐘。
準(zhǔn)備顯示時(shí)鐘
在設(shè)置時(shí)鐘樣式之前,我們需要進(jìn)行一些細(xì)化。
- 消除初始延遲,使您的時(shí)鐘立即顯示。
- 提高時(shí)鐘腳本的效率,以免持續(xù)重建整個(gè)時(shí)鐘。
- 根據(jù)需要添加前導(dǎo)零。
消除初始延遲
在時(shí)鐘中,我們習(xí)慣于setInterval每秒更新一次顯示。多數(shù)情況下,這很好,除非在開始時(shí)會(huì)有一秒鐘的延遲。要消除此延遲,我們必須在間隔開始之前更新一次時(shí)鐘。
讓我們將要傳遞給setInterval它的匿名函數(shù)移到其自己的獨(dú)立函數(shù)中。我們可以命名這個(gè)函數(shù)updateClock。在updateClock外部調(diào)用該函數(shù)setInterval,然后在內(nèi)部再次調(diào)用setInterval。這樣,時(shí)鐘顯示就沒有延遲了。
在您的JavaScript中,替換為:
- const timeinterval = setInterval(() => { ... },1000);
有了這個(gè):
- function updateClock(){
- const t = getTimeRemaining(endtime);
- clock.innerHTML = 'days: ' + t.days + '<br>' +
- 'hours: '+ t.hours + '<br>' +
- 'minutes: ' + t.minutes + '<br>' +
- 'seconds: ' + t.seconds;
- if (t.total <= 0) {
- clearInterval(timeinterval);
- }
- }
- updateClock(); // run function once at first to avoid delay
- var timeinterval = setInterval(updateClock,1000);
避免持續(xù)重建時(shí)鐘
我們需要使時(shí)鐘腳本更高效。我們只想更新時(shí)鐘中的數(shù)字,而不是每秒重新構(gòu)建整個(gè)時(shí)鐘。實(shí)現(xiàn)此目的的一種方法是將每個(gè)數(shù)字放在span標(biāo)簽中,然后僅更新這些跨度的內(nèi)容。
這是HTML:
- <div id="clockdiv">
- Days: <span class="days"></span><br>
- Hours: <span class="hours"></span><br>
- Minutes: <span class="minutes"></span><br>
- Seconds: <span class="seconds"></span>
- </div>
現(xiàn)在讓我們參考這些元素。在clock定義變量的位置之后添加以下代碼
- const daysSpan = clock.querySelector('.days');
- const hoursSpan = clock.querySelector('.hours');
- const minutesSpan = clock.querySelector('.minutes');
- const secondsSpan = clock.querySelector('.seconds');
接下來,我們需要更改updateClock功能以及更新數(shù)字。新代碼如下所示:
- function updateClock(){
- const t = getTimeRemaining(endtime);
- daysSpan.innerHTML = t.days;
- hoursSpan.innerHTML = t.hours;
- minutesSpan.innerHTML = t.minutes;
- secondsSpan.innerHTML = t.seconds;
- ...
- }
添加前導(dǎo)零
現(xiàn)在時(shí)鐘不再每秒都在重建,我們還有另一件事要做:添加前導(dǎo)零。例如,不是讓時(shí)鐘顯示7秒,而是顯示07秒。一種簡(jiǎn)單的方法是在數(shù)字的開頭添加字符串“ 0”,然后切掉最后兩位數(shù)字。
例如,要在“ seconds”值上添加前導(dǎo)零,您可以對(duì)此進(jìn)行更改:
- secondsSpan.innerHTML = t.seconds;
對(duì)此:
- secondsSpan.innerHTML = ('0' + t.seconds).slice(-2);
如果需要,您也可以在分鐘和小時(shí)中添加前導(dǎo)零。如果您走了這么遠(yuǎn),恭喜!您的時(shí)鐘現(xiàn)在可以顯示了。
注意:您可能需要在CodePen中單擊“重新運(yùn)行”才能開始倒計(jì)時(shí)。
更進(jìn)一步
下面的示例演示如何針對(duì)某些用例擴(kuò)展時(shí)鐘。它們都是基于上面看到的基本示例。
自動(dòng)安排時(shí)鐘
假設(shè)我們希望時(shí)鐘顯示在某些日子,而不是其他日子。例如,我們可能會(huì)發(fā)生一系列事件,并且不想每次都手動(dòng)更新時(shí)鐘。這是提前安排事情的方法。
通過在CSS中將其display屬性設(shè)置為隱藏時(shí)鐘none。然后將以下內(nèi)容添加到initializeClock函數(shù)中(以開頭的行之后var clock)。一旦initializeClock調(diào)用此函數(shù),這將導(dǎo)致時(shí)鐘僅顯示:
- clock.style.display = 'block';
接下來,我們可以指定顯示時(shí)鐘的日期。這將替換deadline變量:
- const schedule = [
- ['Jul 25 2015', 'Sept 20 2015'],
- ['Sept 21 2015', 'Jul 25 2016'],
- ['Jul 25 2016', 'Jul 25 2030']
- ];
schedule數(shù)組中的每個(gè)元素代表一個(gè)開始日期和一個(gè)結(jié)束日期。如上所述,可以包括時(shí)間和時(shí)區(qū),但是我在這里使用了簡(jiǎn)單的日期來保持代碼的可讀性。
最后,當(dāng)用戶加載頁(yè)面時(shí),我們需要檢查是否在指定的時(shí)間范圍內(nèi)。該代碼應(yīng)替換先前對(duì)該initializeClock函數(shù)的調(diào)用。
- // iterate over each element in the schedule
- for (var i=0; i<schedule.length; i++) {
- var startDate = schedule[i][0];
- var endDate = schedule[i][1];
- // put dates in milliseconds for easy comparisons
- var startMs = Date.parse(startDate);
- var endMs = Date.parse(endDate);
- var currentMs = Date.parse(new Date());
- // if current date is between start and end dates, display clock
- if (endMs > currentMs && currentMs >= startMs ) {
- initializeClock('clockdiv', endDate);
- }
- }
- schedule.forEach(([startDate, endDate]) => {
- // put dates in milliseconds for easy comparisons
- const startMs = Date.parse(startDate);
- const endMs = Date.parse(endDate);
- const currentMs = Date.parse(new Date());
- // if current date is between start and end dates, display clock
- if (endMs > currentMs && currentMs >= startMs ) {
- initializeClock('clockdiv', endDate);
- }
- });
現(xiàn)在,您可以提前安排時(shí)鐘,而無需手動(dòng)更新。您可以根據(jù)需要縮短代碼。為了便于閱讀,我讓我變得冗長(zhǎng)。
從用戶到達(dá)起將計(jì)時(shí)器設(shè)置為10分鐘
用戶到達(dá)或開始特定任務(wù)后,有必要在給定的時(shí)間內(nèi)設(shè)置倒計(jì)時(shí)。我們將在此處將計(jì)時(shí)器設(shè)置為10分鐘,但是您可以使用任意時(shí)間。
我們需要做的就是deadline用這個(gè)替換變量:
- const timeInMinutes = 10;
- const currentTime = Date.parse(new Date());
- const deadline = new Date(currentTime + timeInMinutes*60*1000);
該代碼將花費(fèi)當(dāng)前時(shí)間,并增加十分鐘。這些值將轉(zhuǎn)換為毫秒,因此可以將它們加在一起并變成新的截止日期。
現(xiàn)在,我們有了一個(gè)時(shí)鐘,可以從用戶到達(dá)時(shí)開始倒數(shù)十分鐘。隨意玩耍,嘗試不同的時(shí)間長(zhǎng)度。
跨頁(yè)面保持時(shí)鐘進(jìn)度
有時(shí),有必要將時(shí)鐘狀態(tài)保留的時(shí)間不僅限于當(dāng)前頁(yè)面。如果我們想在整個(gè)網(wǎng)站上設(shè)置10分鐘的計(jì)時(shí)器,則我們不希望在用戶轉(zhuǎn)到其他頁(yè)面時(shí)將其重置。
一種解決方案是將時(shí)鐘的結(jié)束時(shí)間保存在cookie中。這樣,導(dǎo)航到新頁(yè)面不會(huì)將結(jié)束時(shí)間重置為現(xiàn)在的十分鐘。
這是邏輯:
如果Cookie中記錄了截止日期,請(qǐng)使用該截止日期。
如果不存在該cookie,則設(shè)置一個(gè)新的截止日期并將其存儲(chǔ)在cookie中。
要實(shí)現(xiàn)這一點(diǎn),請(qǐng)用deadline以下內(nèi)容替換變量:
- let deadline;
- // if there's a cookie with the name myClock, use that value as the deadline
- if(document.cookie && document.cookie.match('myClock')){
- // get deadline value from cookie
- deadline = document.cookie.match(/(^|;)myClock=([^;]+)/)[2];
- } else {
- // otherwise, set a deadline 10 minutes from now and
- // save it in a cookie with that name
- // create deadline 10 minutes from now
- const timeInMinutes = 10;
- const currentTime = Date.parse(new Date());
- deadline = new Date(currentTime + timeInMinutes*60*1000);
- // store deadline in cookie for future reference
- document.cookie = 'myClock=' + deadline + '; path=/; domain=.yourdomain.com';
- }
這段代碼利用了cookie和正則表達(dá)式,它們本身就是單獨(dú)的主題。因此,我在這里不再贅述。需要注意的一件事是,您需要更改.yourdomain.com為實(shí)際域。
有關(guān)客戶端事件的重要警告
JavaScript日期和時(shí)間是從用戶計(jì)算機(jī)中獲取的。這意味著用戶可以通過更改計(jì)算機(jī)上的時(shí)間來影響JavaScript時(shí)鐘。在大多數(shù)情況下,這無關(guān)緊要。但是,在某些超級(jí)敏感的情況下,有必要從服務(wù)器獲取時(shí)間??梢允褂靡恍㏄HP或Ajax來完成,這兩者都超出了本教程的范圍。
從服務(wù)器獲取時(shí)間后,我們可以使用本教程中的相同技術(shù)來使用它。
加起來
閱讀完本文中的示例之后,您現(xiàn)在知道如何僅用幾行原始JavaScript代碼創(chuàng)建自己的倒數(shù)計(jì)時(shí)器!我們已經(jīng)研究了如何制作基本的倒數(shù)時(shí)鐘并有效顯示它。我們還介紹了添加一些有用的附加功能,包括計(jì)劃,絕對(duì)時(shí)間和相對(duì)時(shí)間,以及使用Cookie保留頁(yè)面和站點(diǎn)訪問之間的狀態(tài)。
完整代碼
- <!DOCTYPE html>
- <html lang="zh">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <meta http-equiv="X-UA-Compatible" content="ie=edge" />
- <title>Countdown Clock</title>
- <style type="text/css">
- body{
- text-align: center;
- background: #00ECB9;
- font-family: sans-serif;
- font-weight: 100;
- }
- h1{
- color: #396;
- font-weight: 100;
- font-size: 40px;
- margin: 40px 0px 20px;
- }
- #clockdiv{
- font-family: sans-serif;
- color: #fff;
- display: inline-block;
- font-weight: 100;
- text-align: center;
- font-size: 30px;
- }
- #clockdiv > div{
- padding: 10px;
- border-radius: 3px;
- background: #00BF96;
- display: inline-block;
- }
- #clockdiv div > span{
- padding: 15px;
- border-radius: 3px;
- background: #00816A;
- display: inline-block;
- }
- .smalltext{
- padding-top: 5px;
- font-size: 16px;
- }
- </style>
- </head>
- <body>
- <h1>Countdown Clock</h1>
- <div id="clockdiv">
- <div>
- <span class="days"></span>
- <div class="smalltext">Days</div>
- </div>
- <div>
- <span class="hours"></span>
- <div class="smalltext">Hours</div>
- </div>
- <div>
- <span class="minutes"></span>
- <div class="smalltext">Minutes</div>
- </div>
- <div>
- <span class="seconds"></span>
- <div class="smalltext">Seconds</div>
- </div>
- </div>
- <script type="text/javascript">
- function getTimeRemaining(endtime) {
- const total = Date.parse(endtime) - Date.parse(new Date());
- const seconds = Math.floor((total / 1000) % 60);
- const minutes = Math.floor((total / 1000 / 60) % 60);
- const hours = Math.floor((total / (1000 * 60 * 60)) % 24);
- const days = Math.floor(total / (1000 * 60 * 60 * 24));
- return {
- total,
- days,
- hours,
- minutes,
- seconds
- };
- }
- function initializeClock(id, endtime) {
- const clock = document.getElementById(id);
- const daysSpan = clock.querySelector('.days');
- const hoursSpan = clock.querySelector('.hours');
- const minutesSpan = clock.querySelector('.minutes');
- const secondsSpan = clock.querySelector('.seconds');
- function updateClock() {
- const t = getTimeRemaining(endtime);
- daysSpan.innerHTML = t.days;
- hoursSpan.innerHTML = ('0' + t.hours).slice(-2);
- minutesSpan.innerHTML = ('0' + t.minutes).slice(-2);
- secondsSpan.innerHTML = ('0' + t.seconds).slice(-2);
- if (t.total <= 0) {
- clearInterval(timeinterval);
- }
- }
- updateClock();
- const timeinterval = setInterval(updateClock, 1000);
- }
- const deadline = new Date(Date.parse(new Date()) + 15 * 24 * 60 * 60 * 1000);
- initializeClock('clockdiv', deadline);
- </script>
- </body>
- </html>