Anthony Chao

JS30 day29 Countdown Timer

#Javascript

JS30 day29 - Countdown Timer

作業內容

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

學到什麼

  • JS 1. 直覺想到倒數的功能是使用 setInterval 可是這會有幾個比較不友好的點:比方說在 ios 上面 scroll 的時候他自動會把這功能停掉,你滾動 10 秒的話倒數就少了 10 秒,所以建議不要在這邊用 setInterval
    function timer(seconds){
      setInterval(function(){
        seconds--;
      }, 1000)
    }
    1. 讓 setInterval 在倒數完之後不繼續運作 最直覺想到的可能會是這樣:
      setInterval(function(){
        const secondsLeft = Math.round((then - Date.now()) / 1000)
        if(secondsLeft < 0) return;
        console.log(secondsLeft)
      }, 1000)
    但 return 不能真正解決問題,他只是不繼續往下執行,每秒還是會觸發這個事件,所以要用 cleanInterval 我們要在他自己的方法裡面停掉自己,所以先做出一個 global 的 coundown 變數再用 clearInterval 停掉它
    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 因此我們要改成這樣
    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 的效果會疊加,所以在開始另一個之前要先把先前的停掉
    function timer(seconds){
    // 開始另一個 timer 之前先把之前的 setInterval 停掉
      clearInterval(countdown)
    // 下略
    1. 我們可以直接用 document.<form 的 name> 這個方式來呼叫 form,可以再用一層呼叫 input 的 name
    <form name="customForm" id="custom">
      <input type="text" name="minutes" placeholder="Enter Minutes">
    </form>
    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:

<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:

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:

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()
})