[Javascript] 29. Countdown Timer
1. 개요
카운트다운 기능을 만든다.
2. HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Countdown Timer</title>
<link href='https://fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="style.css">
</head>
<body>
<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>
<script src="scripts-START.js"></script>
</body>
</html>
3. 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;
}
4. Javascript
let countdown;
const timerDisplay = document.querySelector('.display__time-left');
const endTime = document.querySelector('.display__end-time');
const buttons = document.querySelectorAll('[data-time]');
먼저 전역변수로 countdown을 선언하고, 나머지 필요한 부분들도 선택하여 변수로 선언한다.
function timer(seconds) {
clearInterval(countdown);
const now = Date.now();
const then = now + seconds * 1000;
displayTimeLeft(seconds);
displayEndTime(then);
countdown = setInterval(() => {
const secondsLeft = Math.round((then - Date.now()) / 1000);
if (secondsLeft < 0) {
clearInterval(countdown);
return;
}
displayTimeLeft(secondsLeft);
}, 1000);
}
timer 함수를 만든다. timer는 가장 먼저 clearInterval로 countdown을 중단시킨다. clearInterval은 setInterval로 반복 중인 함수를 멈추게 하는데, countdown을 여러 번 요청하는 경우 각각의 요청에 따른 시행이 병렬적으로 반복 수행되기 때문이다.
이후, 현재 시간을 now에 할당하고, 전달 받은 변수인 seconds에 1000을 곱한 값을 더해 then에 할당한다. 1000을 곱하는 이유는, 받은 변수는 밀리초 단위로 전달될 것이기 때문에 초 단위로 화면에 출력하기 위함이고, then은 카운트다운이 끝난 시점의 시간을 표현하게 될 것이다.
displayTimeLeft에는 seconds를 전달하여 카운트다운을 표시할 것이고, displayEndTime에는 then을 전달하여 카운트다운이 종료되는 시간을 보이게 할 것이다.
countdown의 내용은 setInterval로 설정하고, setInterval에서 반복 수행될 함수는 다음과 같다. secondsLeft 변수에, then에서 now를 뺀 값을 다시 1000으로 나눈 값을 할당한다. 값이 변하지 않는다면 seconds와 같지만 seconds를 입력해버리면 인터벌의 반복 때마다 같은 값이 표시되므로 번거롭지만 더했다가 다시 빼 주는 값을 표시해야 한다. 이렇게 설정한 secondsLeft는 0보다 작게 되면 clearInterval을 통해 중단시키고, displayTimeLeft의 인자로 전달되어 화면에 표시된다.
function displayTimeLeft(seconds) {
const minutes = Math.floor(seconds / 60);
const remainderSeconds = seconds % 60;
const display = `${minutes}:${remainderSeconds < 10 ? '0' : ''}${remainderSeconds}`;
document.title = display;
timerDisplay.textContent = display;
}
displayTimeLeft는 받은 초를 분과 초로 표시할 것이다. 받은 초를 60으로 나누어 내림한 값을 minutes에 할당하고, 그 나머지 초들을 remainderSeconds에 할당한다. display 변수에 이들을 원하는 형식으로 표현하여 할당하고, 문서의 title, timerDisplay.textContent에 입력한다.
function displayEndTime(timestamp) {
const end = new Date(timestamp);
const hour = end.getHours();
const adjustedHour = hour > 12 ? hour - 12 : hour;
const minutes = end.getMinutes();
endTime.textContent = `Be Back At ${adjustedHour}:${minutes < 10 ? '0' : ''}${minutes}`;
}
displayEndTime는 받은 매개변수(timer 함수에서 전달한 then)를 시간 객체로 표시하는 end에 할당하고, 여기에 시간 정보를 얻는 getHours을 적용하여 hour에 할당한다. 또, adjustedHour에 12진법으로 표현될 수 있도록 조건문을 입력하고, minutes에는 getMinutes를 적용하여 minutes 변수에 할당한다. 이들을 이용하여 endTime의 textContent에 원하는 내용으로 출력될 수 있도록 변경한다.
function startTimer() {
const seconds = parseInt(this.dataset.time);
timer(seconds);
}
buttons.forEach(button => button.addEventListener('click', startTimer));
정해진 몇 개의 버튼을 누르면 카운트다운을 시작하도록 모든 버튼에 클릭 이벤트 리스너를 걸고 startTimer 함수를 만든다. HTML의 데이터셋에 각각의 시간을 입력해 두었기 때문에, 이를 정수로 변환한 값을 seconds에 할당하고, timer에 전달함으로써 위에서 정의한 함수들이 모두 실행된다.
document.customForm.addEventListener('submit', function(e) {
e.preventDefault();
const mins = this.minutes.value;
timer(mins * 60);
this.reset();
});
또, 사용자가 입력하는 숫자를 받아 카운트다운을 시행하기 위해, customForm에 submit 이벤트 리스너를 건다. 이에 대한 콜백은, 새로고침을 방지하기 위해 preventDefault를 시행하고, this.minutes.value를 mins에 할당한다. this는 이벤트의 대상인 입력 폼인데, HTML에서 이 태그의 name으로 minutes를 주었고, value는 입력된 값을 전달받을 것이다. 이렇게 전달 받은 분에 60을 곱해 timer로 전달하면 입력 받은 시간만큼의 카운트다운을 시작할 것이다. 재입력을 위하여 this.reset을 마지막에 입력해 주면 완성된다.