프레임워크 다루는데 집중했었던 일과 그간 너무 오래 쉬었던 탓인지 프로젝트 진행에 대한 감이라던가, 자바스크립트를 쓰던 감을 다 잊어버려서 간단하게 진행할 수 있으면서도 도움이 될만한 것이 무엇인지 생각해보다가 타이머 앱이 떠올라서 만들어보자 하고 만들게 되었습니다.
서버가 필요없는 정적 웹페이지라 배포는 GitHub Pages
를 통해 배포하였습니다.
Bootstrap
프레임워크도 함께 사용했습니다.전체 코드는 깃 허브를 참조해주세요.
전체 코드를 적고 설명을 넣었더니 글이 너무 읽기 싫게 변해버려서 그냥 기능과 해당 부분의 코드만 뽑아서 소개드리도록 하겠습니다. 소스 코드 파일에서 필요한 부분만 발췌했으므로 전체 코드가 필요하신분들은 깃 허브 리포지토리를 참조해주세요. 파일에서 생략된 부분에 (...)
표시를 해두었습니다.
index.js
에서는 요소에 이벤트를 등록합니다. 이 타이머 앱은 버튼들로 이벤트를 처리하므로 모두 마우스 조작과 관련한 이벤트만이 있습니다.
- 다크 모드에 대한 이벤트입니다. Button이라고 이름을 붙였지만
type=checkbox
속성을 이용해서 버튼인 것 처럼 속였습니다. 그래서 checked 상태를 가지고 다크 모드와 라이트 모드를 판별하도록 했습니다.darkModeButton.addEventListener('click', e => { if (e.target.checked) { changeDarkMode(); currentMode = 'dark'; } else { changeLightMode() currentMode = 'light'; } });
- 다크 모드 전환 버튼 위에 마우스를 올리면 해당 모드에 맞춰 이미지가 점멸되는 이벤트입니다.
darkModeImg.addEventListener('mouseover', () => { if (currentMode === 'light') { darkModeImg.src = './icons/brightness-high-fill.svg'; } else { darkModeImg.src = './icons/moon.svg'; } }); darkModeImg.addEventListener('mouseout', () => { if (currentMode === 'light') { darkModeImg.src = './icons/brightness-high.svg'; } else { darkModeImg.src = './icons/moon-fill.svg'; } }); });
start 버튼을 클릭했을 때의 동작입니다. start와 pause를 한 버튼을 공유하도록 설계해서 누른 버튼의 innerText
값에 따라 해당 동작을 수행합니다.
- 먼저 input 값을 가져와서 시간 정보를 취득합니다. 시간은 초 단위로 통합해서 동작을 하도록 했습니다. 이 과정에서 입력된 값이 음수이거나, 숫자가 아니라면 오류를 발생시키고 해당 함수를 종료시킵니다.
const time = [parseInt(hour.value), parseInt(minute.value), parseInt(second.value)]; if (time[0] < 0 || time[1] < 0 || time[2] < 0) { window.alert('유효한 값이 아닙니다. (음수 입력)'); hour.value = ''; minute.value = ''; second.value = ''; return; } let totalSeconds = time[0] * 60 * 60 + time[1] * 60 + time[2]; if (Number.isNaN(totalSeconds)) { window.alert('유효한 값이 아닙니다. (숫자 외의 문자 입력)'); hour.value = ''; minute.value = ''; second.value = ''; return; }
입력값이 유효하다면 입력 필드를
disabled
시켜 추가 입력을 방지합니다. 그리고disabled
되어 조작이 불가능했던 reset 버튼을 누를 수 있도록 활성화 시킵니다.이후 버튼의 값에 따라
start
라면 타이머를 시작하게 만들고,pause
라면 Interval을 clear해서 초가 더이상 흐르지 않도록 시킵니다.hour.disabled = true; minute.disabled = true; second.disabled = true; document.getElementById('resetButton').disabled = false; if (startAndPauseButton.innerText === 'start') { startAndPauseButton.innerText = 'pause'; timerAction(totalSeconds); } else { startAndPauseButton.innerText = 'start'; clearInterval(timerInterval); }
reset
은 타이머를 초기 상태로 초기화시키는 버튼입니다.
타이머가 작동되는 동안
disabled
됐었던 입력 필드를 다시 활성화 시키고, 필드를 빈 문자열로 덮어 씌워서 비워냅니다.그 후 다시 reset버튼을 비활성화 시키고, start/pause 버튼을 start 상태로 되돌립니다. 마지막으로 작동중이던
Interval
도 clear 시켜서 완전히 초기 상태로 되돌립니다.export const resetButtonAction = () => { const hour = document.getElementById('input-hour'); const minute = document.getElementById('input-minute'); const second = document.getElementById('input-second'); hour.disabled = false; hour.value = ''; minute.disabled = false; minute.value = ''; second.disabled = false; second.value = ''; document.getElementById('resetButton').disabled = true; document.getElementById('startAndPauseButton').innerText = 'start'; clearInterval(timerInterval); };
타이머의 핵심 동작을 하는 timerAction.js
입니다.
timerInterbal
이export
를 통해 선언되었는데, 이 이유는 위에서 본 puase와 reset에서 인터벌을 멈추거나 초기화 하기 위해 인터벌 변수를 export를 통해 외부에서 사용할 수 있게끔 선언한 것 입니다.타이머 동작은
setInterval()
메소드를 통해 1초(1000밀리초)마다 전체 초에서 -1씩 되도록 설계했습니다. input 필드에 보여지는 시간초 숫자는updateInput()
함수를 만들어서 업데이트 상태를 반영하도록 했습니다.마지막으로 총합 시간이 0가 되면 alert 창으로 시간 종료를 알리고 reset 함수에서 했던 초괴화 동작을 수행합니다.
export let timerInterval; export const timerAction = totalSeconds => { const hour = document.getElementById('input-hour'); const minute = document.getElementById('input-minute'); const second = document.getElementById('input-second'); timerInterval = setInterval(() => { totalSeconds--; updateInput(totalSeconds); if (totalSeconds <= 0) { window.alert('시간종료'); hour.disabled = false; minute.disabled = false; second.disabled = false; clearInterval(timerInterval); } }, 1000); };
updateInput
은 timerAction
함수에서 초가 줄어듦에 따라 감소하는 초를 사용자에게 보이기 위해 input 창에 시간이 줄어드는 상태를 업데이트해서 보여주는 함수입니다.
- 남은 총 시간에서 각각 시, 분, 초를 계산해 나온 값을 변수에 담아 입력 필드의 value 값으로 만들어줍니다.
- 계산 식에서
~~
는Math.floor
와 같은 역할(소수점 반올림)을 합니다. ⛔export const updateInput = totalSeconds => { const hour = document.getElementById('input-hour'); const minute = document.getElementById('input-minute'); const second = document.getElementById('input-second'); const currentHour = ~~(totalSeconds / 60 / 60); const currentMinute = ~~((totalSeconds / 60) % 60); const currentSecond = totalSeconds % 60; hour.value = currentHour; minute.value = currentMinute; second.value = currentSecond; };
changeDarkMode.js
는 다크 모드 버튼을 눌렀을 때 다크 모드로 전환되는 동작을 합니다.
다크 모드의 전환 방식은 <body>
태그의 class
속성으로 'dark-mode'
라는 값을 주어서 해당 속성값이 존재하는 경우 다크 모드로, 그렇지 않으면 라이트 모드로 인식하도록 설계했습니다. css가 <body>
의 class
속성을 체크하고 해당 속성에 맞는 모드의 css를 적용시켜줍니다.
<body>
태그에class='dark-mode'
를 추가합니다. 다크 모드가 적용될 요소에 모두 추가가 됩니다.- 또한 다크 모드 진입시 다크 모드 전환 버튼의 아이콘을 해에서 달 그림으로 바꿔줍니다.
export const changeDarkMode = () => { document.body.classList.add('dark-mode'); document.getElementById('dark-mode-img').src = './icons/moon-fill.svg'; document.getElementById('input-hour').classList.add('dark-mode'); document.getElementById('input-minute').classList.add('dark-mode'); document.getElementById('input-second').classList.add('dark-mode'); document.getElementById('startAndPauseButton').classList.add('dark-mode'); document.getElementById('resetButton').classList.add('dark-mode'); };
🌙다크 모드에 대하여...🌙
개인적인 경험으로 완전 까만 배경에 흰색 조합보단, 짙은 회색에 가까운 배경색에 회색~옅은 회색의 글자 조합으로 이루어진 다크 모드가 더 편하게 느껴졌기에 다크 모드를 완전한 검은색보다는 회색 계열로 이루어지도록 디자인을 작성했습니다.
반대로 다크 모드에서 라이트 모드로 전환되는 경우의 동작입니다. 이 경우에서는 이전 동작에서 추가됐었던 class='dark-mode'
를 삭제하는 방식으로 동작합니다.
export const changeLightMode = () => { document.body.classList.remove('dark-mode'); document.getElementById('dark-mode-img').src = './icons/brightness-high.svg'; document.getElementById('input-hour').classList.remove('dark-mode'); document.getElementById('input-minute').classList.remove('dark-mode'); document.getElementById('input-second').classList.remove('dark-mode'); document.getElementById('startAndPauseButton').classList.remove('dark-mode'); document.getElementById('resetButton').classList.remove('dark-mode'); };
Bootstarp
프레임워크를 이번 기회로 사용할 줄 알게 되었다.Clean Code
를 읽어 놓고도 잘 지키지 못했다.z-index: -1;
을 하지 말자!(css)감도 되찾고, 배우고 싶었던 기술들(
Bootstarp
,다크 모드
)를 배우고 써볼 수 있는 귀중한 기회였다. 그리고 설계의 중요성을 뼈저리게 깨달아버린 프로젝트였다!