타이머 만들기 (with JavaScript)

Bam·2023년 2월 23일
0

projects

목록 보기
1/4
post-thumbnail

⏱기획의도⏱

프레임워크 다루는데 집중했었던 일과 그간 너무 오래 쉬었던 탓인지 프로젝트 진행에 대한 감이라던가, 자바스크립트를 쓰던 감을 다 잊어버려서 간단하게 진행할 수 있으면서도 도움이 될만한 것이 무엇인지 생각해보다가 타이머 앱이 떠올라서 만들어보자 하고 만들게 되었습니다.


⏱프로젝트⏱

결과물

서버가 필요없는 정적 웹페이지라 배포는 GitHub Pages를 통해 배포하였습니다.


사용 기술

  • HTML: 웹 페이지 화면 구성에 사용했습니다.
  • CSS + Bootstrap: 웹 페이지 화면 디자인에 사용했습니다. 좀 더 깔끔하고 편리한 디자인을 위해 Bootstrap 프레임워크도 함께 사용했습니다.
  • JavaScript: 요소의 이벤트 처리, 타이머 앱의 동작에 사용했습니다.

기능 소개

전체 코드는 깃 허브를 참조해주세요.

전체 코드를 적고 설명을 넣었더니 글이 너무 읽기 싫게 변해버려서 그냥 기능과 해당 부분의 코드만 뽑아서 소개드리도록 하겠습니다. 소스 코드 파일에서 필요한 부분만 발췌했으므로 전체 코드가 필요하신분들은 깃 허브 리포지토리를 참조해주세요. 파일에서 생략된 부분에 (...)표시를 해두었습니다.

index.js

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';
    }
  });
});

startAndPauseButtonAction.js

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

resetButtonAction.js

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

타이머의 핵심 동작을 하는 timerAction.js입니다.

  • timerInterbalexport를 통해 선언되었는데, 이 이유는 위에서 본 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.js

updateInputtimerAction 함수에서 초가 줄어듦에 따라 감소하는 초를 사용자에게 보이기 위해 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

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');
};

🌙다크 모드에 대하여...🌙
개인적인 경험으로 완전 까만 배경에 흰색 조합보단, 짙은 회색에 가까운 배경색에 회색~옅은 회색의 글자 조합으로 이루어진 다크 모드가 더 편하게 느껴졌기에 다크 모드를 완전한 검은색보다는 회색 계열로 이루어지도록 디자인을 작성했습니다.


changeLightMode.js

반대로 다크 모드에서 라이트 모드로 전환되는 경우의 동작입니다. 이 경우에서는 이전 동작에서 추가됐었던 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, 다크 모드)를 배우고 써볼 수 있는 귀중한 기회였다. 그리고 설계의 중요성을 뼈저리게 깨달아버린 프로젝트였다!

0개의 댓글