JS30 day29 Countdown Timer

Posted by Anthony Chao on 2019-11-30

JS30 day29 - Countdown Timer

作業內容

今天的作業是做出一個倒數的頁面,有按鈕可按,也可以自訂要倒數幾分鐘
雖然聽起來頗為簡單,但做起來要超多細節要注意
可以看這份 codepen

學到什麼

  • JS

    直覺想到倒數的功能是使用 setInterval
    可是這會有幾個比較不友好的點:比方說在 ios 上面 scroll 的時候他自動會把這功能停掉,你滾動 10 秒的話倒數就少了 10 秒,所以建議不要在這邊用 setInterval

    1
    2
    3
    4
    5
    function timer(seconds){
    setInterval(function(){
    seconds--;
    }, 1000)
    }
    1. 讓 setInterval 在倒數完之後不繼續運作
      最直覺想到的可能會是這樣:
    1
    2
    3
    4
    5
    setInterval(function(){
    const secondsLeft = Math.round((then - Date.now()) / 1000)
    if(secondsLeft < 0) return;
    console.log(secondsLeft)
    }, 1000)

    但 return 不能真正解決問題,他只是不繼續往下執行,每秒還是會觸發這個事件,所以要用 cleanInterval
    我們要在他自己的方法裡面停掉自己,所以先做出一個 global 的 coundown 變數再用 clearInterval 停掉它

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let countdown

    function timer(seconds){
    // const now = (new Date()).getTime()
    // 上面這種寫法也可以
    const now = Date.now() // mili seconds 的形式
    const then = now + seconds * 1000
    countdown = setInterval(function(){
    const secondsLeft = Math.round((then - Date.now()) / 1000)
    if(secondsLeft < 0) {
    clearInterval(countdown)
    return
    }
    console.log(secondsLeft)
    }, 1000)
    }
    1. 有個很細節的地方:目前我們呼叫 timer 的時候,他當下不會觸發時間,而是過一秒之後才開始,可以看下圖,想要的效果是如果呼叫 timer(10) 當下會先說出 10

      因此我們要改成這樣
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function timer(seconds){
    // const now = (new Date()).getTime()
    // 上面這種寫法也可以
    const now = Date.now() // mili seconds 的形式
    const then = now + seconds * 1000

    displayTimeLeft(seconds)

    countdown = setInterval(function(){
    const secondsLeft = Math.round((then - Date.now()) / 1000)
    if(secondsLeft < 0) {
    clearInterval(countdown)
    return
    }
    displayTimeLeft(secondsLeft)
    }, 1000)
    }

    function displayTimeLeft(seconds){
    console.log(seconds)
    }
    1. 如果在按下一個 timer 又按另一個 timer 之後,這些 setInterval 的效果會疊加,所以在開始另一個之前要先把先前的停掉
    1
    2
    3
    4
    function timer(seconds){
    // 開始另一個 timer 之前先把之前的 setInterval 停掉
    clearInterval(countdown)
    // 下略
    1. 我們可以直接用 document.<form 的 name> 這個方式來呼叫 form,可以再用一層呼叫 input 的 name
    1
    2
    3
    <form name="customForm" id="custom">
    <input type="text" name="minutes" placeholder="Enter Minutes">
    </form>
    1
    2
    3
    4
    document.customForm
    // <form name="customForm" id="custom">
    document.customForm.minutes
    // <input type="text" name="minutes" placeholder="Enter Minutes">

https://github.com/wesbos/JavaScript30

code 內容:

HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="timer">
<div class="timer__controls">
<button data-time="20" class="timer__button">20 Secs</button>
<button data-time="300" class="timer__button">Work 5</button>
<button data-time="900" class="timer__button">Quick 15</button>
<button data-time="1200" class="timer__button">Snack 20</button>
<button data-time="3600" class="timer__button">Lunch Break</button>
<form name="customForm" id="custom">
<input type="text" name="minutes" placeholder="Enter Minutes">
</form>
</div>
<div class="display">
<h1 class="display__time-left"></h1>
<p class="display__end-time"></p>
</div>
</div>

CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
html {
box-sizing: border-box;
font-size: 10px;
background: #8E24AA;
background: linear-gradient(45deg, #42a5f5 0%,#478ed1 50%,#0d47a1 100%);
}

*, *:before, *:after {
box-sizing: inherit;
}

body {
margin: 0;
text-align: center;
font-family: 'Inconsolata', monospace;
}

.display__time-left {
font-weight: 100;
font-size: 20rem;
margin: 0;
color: white;
text-shadow: 4px 4px 0 rgba(0,0,0,0.05);
}

.timer {
display: flex;
min-height: 100vh;
flex-direction: column;
}

.timer__controls {
display: flex;
}

.timer__controls > * {
flex: 1;
}

.timer__controls form {
flex: 1;
display: flex;
}

.timer__controls input {
flex: 1;
border: 0;
padding: 2rem;
}

.timer__button {
background: none;
border: 0;
cursor: pointer;
color: white;
font-size: 2rem;
text-transform: uppercase;
background: rgba(0,0,0,0.1);
border-bottom: 3px solid rgba(0,0,0,0.2);
border-right: 1px solid rgba(0,0,0,0.2);
padding: 1rem;
font-family: 'Inconsolata', monospace;
}

.timer__button:hover,
.timer__button:focus {
background: rgba(0,0,0,0.2);
outline: 0;
}

.display {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

.display__end-time {
font-size: 4rem;
color: white;
}

JS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
let countdown
const timerDisplay = document.querySelector('.display__time-left')
const endTime = document.querySelector('.display__end-time')
const buttons = document.querySelectorAll('[data-time]')

function timer(seconds){
// 開始另一個 timer 之前先把之前的 setInterval 停掉
clearInterval(countdown)
// const now = (new Date()).getTime()
// 上面這種寫法也可以
const now = Date.now() // mili seconds 的形式
const then = now + seconds * 1000
displayEndTime(then)
displayTimeLeft(seconds)

countdown = setInterval(function(){
const secondsLeft = Math.round((then - Date.now()) / 1000)
if(secondsLeft < 0) {
clearInterval(countdown)
return
}
displayTimeLeft(secondsLeft)
}, 1000)
}

function displayTimeLeft(seconds){
const minutes = Math.floor(seconds / 60)
const remainSeconds = seconds % 60
const display = `${minutes}:${remainSeconds < 10 ? '0' : ''}${remainSeconds}`
document.title = display
timerDisplay.textContent = display
}

function displayEndTime(timestamp){
const end = new Date(timestamp)
const hours = end.getHours()
const minutes = end.getMinutes()
endTime.textContent = `Be Back At ${hours}:${minutes < 10 ? '0' : ''}${minutes}`
}

function startTimer(){
const period = this.dataset.time
timer(period)
}

buttons.forEach(button => button.addEventListener('click', startTimer))

document.customForm.addEventListener('submit', function(e){
e.preventDefault()
const mins = this.minutes.value
timer(mins * 60)
this.reset()
})




prevent_hack