iT邦幫忙

2022 iThome 鐵人賽

DAY 29
0
自我挑戰組

JavaScript 30天挑戰 自學筆記系列 第 29

JS30 自學筆記 Day29_Countdown Clock

  • 分享至 

  • xImage
  •  

倒數一天!!!

今日任務: 倒數計時時鐘

setInterval 失準

倒計時我們可以使用setInterval(),每秒減1

setInterval(() => {
      seconds--;
}, 1000);

但是setInterval()在某些情況下會有失準的問題:當心 setInterval 因瀏覽器節流模式嚴重失準
因此下面我們每次執行都重新Date.now()取得現在時間,再將結束時間 - 現在時間來避免誤差情形發生。

顯示剩下時間

  • 現在時間
    Date.now(): 回傳自 1970/01/01 00:00:00 UTC 起經過的毫秒數
  • 結束時間
    現在時間 + 倒計時秒數 * 1000 (轉為毫秒數)。
function timer(seconds) {
    const now = Date.now();
    const timeUp = now + seconds * 1000;

    setInterval(() => {
        const secondsLeft = (timeUp - Date.now()) / 1000;
        console.log(secondsLeft);
    }, 1000);
}

timer(10);

會有小數

使用Math.round() 函數回傳四捨五入後的近似值。

function timer(seconds) {
    const now = Date.now();
    const timeUp = now + seconds * 1000;

    setInterval(() => {
        const secondsLeft = Math.round((timeUp - Date.now()) / 1000);
        console.log(secondsLeft);
    }, 1000);
}

timer(10);

暫停倒數

clearInterval(intervalID): 取消setInterval()的重複動作。

setInterval()會返回一個intervalID,我們將它存進變數countDown裡面,之後可以讓clearInterval()知道是要停止哪個setInterval()

let countDown;

function timer(seconds) {
    const now = Date.now();
    const timeUp = now + seconds * 1000;

    countDown = setInterval(() => {
        const secondsLeft = Math.round((timeUp - Date.now()) / 1000);
        if(secondsLeft < 0){
            clearInterval(countDown);
            return;
        }
        console.log(secondsLeft);
    }, 1000);
}

渲染到畫面上

讓setInterval()立即執行

setInterval()的定義是每[一段時間(毫秒)]執行一次,所以不會立刻執行,會停[一段時間]才開始執行。
此setInterval每1秒執行一次,會停1秒才開始執行,
我們希望一打開馬上開始執行,所以我們再開一個函式。

function timer(seconds) {
    const now = Date.now();
    const timeUp = now + seconds * 1000;
    displayTimeLeft(seconds);

    countDown = setInterval(() => {
        const secondsLeft = Math.round((timeUp - Date.now()) / 1000);
        if (secondsLeft < 0) {
            clearInterval(countDown);
            return;
        }
        displayTimeLeft(secondsLeft);
    }, 1000);
}

function displayTimeLeft(seconds){
    console.log(seconds);
}

timer(10);

將秒轉成時間渲染到畫面上

const timeLeft = document.querySelector('.display__time-left');

function displayTimeLeft(seconds){
  const mins = Math.floor(seconds / 60);
  const remainderSecs = seconds % 60;
  timeLeft.textContent = `${mins}:${remainderSecs}`;
}

當個位數的時候前面+0

function displayTimeLeft(seconds){
  const mins = Math.floor(seconds / 60);
  const remainderSecs = seconds % 60;
  timeLeft.textContent = `${mins}:${remainderSecs < 10 ? '0' : ''}${remainderSecs}`;
}

改網頁標題

Document.title: 設置網頁的標題

function displayTimeLeft(seconds){
    ...
    const display = `${mins}:${remainderSecs < 10 ? '0' : ''}${remainderSecs}`;
    timeLeft.textContent = display;
    document.title = display;
}

顯示倒數計時結束時的時間

建立特定時間點的 Date 物件

new date(milliseconds):傳入的參數是一個數字,值表示從 1970-01-01 00:00:00 UTC (格林威治標準時間) 開始累計到某時間點的毫秒數 (milliseconds)。

Date.prototype.getHours(): 根據本地時間返回指定日期的小時 ( 0-23 )。
Date.prototype.getMinutes(): 根據本地時間返回指定日期的分鐘 ( 0-59 )。

const endTime = document.querySelector('.display__end-time');

function timer(seconds) {
    ...
    displayTimeLeft(seconds);
    diplayEndTime(timeUp);

    countDown = setInterval(() => {
        ...
    }, 1000);
}

function diplayEndTime(timeUp) {
    const end = new Date(timeUp);
    const hours = end.getHours();
    const mins = end.getMinutes();
    endTime.textContent = `結束時間:${hours}:${mins < 10 ? '0' : ''}${mins}`;
}

從24小時制改為12小時制

function diplayEndTime(timeUp) {
    const end = new Date(timeUp);
    const hours = end.getHours();
    const mins = end.getMinutes();
    const AmPmHours = hours < 12 ? `上午${hours}` : `下午${hours - 12}`;
    endTime.textContent = `結束時間:${AmPmHours}:${mins < 10 ? '0' : ''}${mins}`;
}

時間按鈕

const timeBtns = document.querySelectorAll('[data-time]');
timeBtns.forEach((btn) => btn.addEventListener('click', startTimer));

function startTimer() {
    const seconds = parseInt(this.dataset.time); //將dataset字串轉成數字
    timer(seconds);
}

發生錯誤

連續按按鈕畫面會亂閃,setInterval()太多個,開頭先清除掉所有setInterval

function timer(seconds) {
    clearInterval(countDown); //開頭先清除掉所有setInterval
    
    const now = Date.now();
    const timeUp = now + seconds * 1000;
    displayTimeLeft(seconds);
    diplayEndTime(timeUp);

    countDown = setInterval(() => {
        ...
    }, 1000);
}

輸入時間

從表單中獲取元素

Document.forms:

  • 返回Document中所有<form>元素的列表。
  • 從表單中獲取元素: (詳細)
const selectForm = document.forms[index];
const selectFormElement = document.forms[index].elements[index];

document.forms.customForm:取得表單名稱為customForm的表單集合

停止submit預設轉址動作

e.preventDefault():如果事件可以被取消,就取消事件(即取消事件的預設行為)。但不會影響事件的傳遞,事件仍會繼續傳遞。

document.customForm.addEventListener('submit', inputTime);

function inputTime(e) {
    e.preventDefault();
    const secs = this.minutes.value * 60; //將輸入的數字分鐘數轉成秒數
    timer(secs);
    this.reset(); //清空表單
}

今日學習到的:

  • setInterval():
    • 在某些情況下會有失準的問題:當心 setInterval 因瀏覽器節流模式嚴重失準
    • 定義是每[一段時間(毫秒)]執行一次,所以不會立刻執行,會停[一段時間]才開始執行。
    • 會返回一個intervalID,之後可以讓clearInterval()知道是要停止哪個setInterval()
  • clearInterval(intervalID): 取消setInterval()的重複動作。
  • Date.now(): 回傳自 1970/01/01 00:00:00 UTC 起經過的毫秒數。
  • new date(milliseconds):傳入的參數是一個數字,值表示從 1970-01-01 00:00:00 UTC (格林威治標準時間) 開始累計到某時間點的毫秒數 (milliseconds)。
  • Date.prototype.getHours(): 根據本地時間返回指定日期的小時 ( 0-23 )。
  • Date.prototype.getMinutes(): 根據本地時間返回指定日期的分鐘 ( 0-59 )。
  • Math.round() 函數回傳四捨五入後的近似值。
  • Document.title: 設置網頁的標題
  • Document.forms:
    • 返回Document中所有<form>元素的列表。
    • 從表單中獲取元素: (詳細)
      • document.forms.customForm:取得表單名稱為customForm的表單集合
  • e.preventDefault():如果事件可以被取消,就取消事件(即取消事件的預設行為)。但不會影響事件的傳遞,事件仍會繼續傳遞。

效果連結:連結

參考連結:
MDN: Date
JavaScript Date 時間和日期 - Fooish 程式技術
MDN: Document.forms
JavaScript - 表單元素- Form Element - KingKong Bruce記事
MDN: Event.preventDefault()
JS30


上一篇
JS30 自學筆記 Day28_Video Speed Controller UI
下一篇
JS30 自學筆記 Day30_Whack A Mole Game
系列文
JavaScript 30天挑戰 自學筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言