윤준영, 더불어 성장하는 개발자    카테고리    태그

wavetimer 프로젝트 회고

wavetimer는 React.js를 기반한 웹 프로젝트입니다. 파동방정식을 이용하여 타이머 배경에 마치 물이 파동치는듯한 효과를 구현하였습니다. 또한, 타이머와 연동되어 시간에 따라 물의 양이 변화하는 애니메이션을 구현하였습니다. 이외에 프로젝트에서 구현한 기능은 다음과 같습니다.

  • 라이트/다크 테마 지원
  • Pixi.js를 기반한 배경 구현
  • 애니메이션 큐 구현    애니메이션을 큐에 담아 순서대로 애니메이션을 진행합니다.
  • 집중 타임/휴식 타임 분할    ex) 60분 타이머가 끝나면 10분 휴식을 취할 수 있는 타이머로 변경합니다.
  • 타이머 시간 설정 가능
  • 타이머를 몇 번 반복했는지 표시

아이디어 구상

물의 양으로 시간이 얼마나 남았는지를 확일할 수 있는 타이머가 있으면 매우 직관적이지 않을까??

우리는 타이머에서 시간을 나타내는 숫자 정보를 바탕으로 시간이 얼마나 남았고, 자신이 얼마나 했는지에 대한 정보를 계산합니다. 하지만 만약, 타이머의 숫자 정보가 아닌 직관적으로 잔여시간과 소모시간을 알 수 있다면 어떨까요? 마치 페트병의 음료를 마실 때 음료의 잔량을 쉽게 확인이 가능하듯 타이머도 직관적으로 시간이 얼마나 남았음을 확인할 수 있다면 좋겠다는 아이디어가 생각나 프로젝트를 진행하게 되었습니다.

개발 과정

어렵다고 생각한 것은 쉬웠지만, 반대로 쉽다고 생각한 것은 어려운…

설명의 용이성을 위해 프로젝트의 주요 기능과 함께 과정을 살펴보도록 하겠습니다.

1. 파동 방정식

https://kr.mathworks.com/matlabcentral/fileexchange/59915-simple-wave-equation-solver

🙈 물리학과 분들은 괜찮을지 몰라도, 저는 아니에요… 🙈

파동 방정식을 이해해야 하는가에 대한 고민을 하던 중 다행히도 MATLAB과 해당 공식을 이용해 제작한 프로젝트가 있어, 이 코드를 참고하여 파동(wave) 방정식을 구현하였습니다. wavetimer를 사용하면 보여지는 wave가 총 2개인 것을 알 수 있는데, 그렇기 때문에 wave와 관련된 변수와 함수들을 모두 클래스 안에 담아 구현하였습니다. 또한, wave를 관리하는 wave-manager에서 두 개의 파동을 총괄적으로 관리하고 렌더링합니다.

여기까지 파동 방정식을 바탕으로 파동이 치는 애니메이션을 구현하였습니다. 하지만 여기서 파동을 어떻게 트리거할 수 있을까요? 처음에는 wave의 높이를 가지고 있는 배열에서 특정 인덱스의 값을 낮게 조정하면 될 것이라 생각했습니다. 하지만 이 방식은 파동을 매우 불안정하게 만들었습니다. 이에 대한 원인은 바로 현실과 똑같습니다. 우리가 손가락으로 물을 건드릴 때, 먼저 손가락이 물의 표면을 향해 아래로 움직이고, 그리고 잠시후 손가락의 끝과 물의 표면이 만나고, 그 이후 손가락은 물의 표면 아래로 움직일 것입니다. 하지만 제가 구현했던 방식은 물의 표면을 만나고 표면 아래로 부드럽게 내려가는 것이 아닌 바로 손가락이 물의 표면 아래로 위치하도록 했기 때문에, 물의 파동이 불안정할 수 밖에 없었습니다. 그렇기 때문에 Sine 함수를 이용해서 특정 인덱스의 파동 높이 값을 부드럽게 하강할 수 있도록 구현함으로써 자연스러운 파동을 유도할 수 있었습니다. 또한, 타이머 시작시 발생하는 파동 애니메이션을 구현하기 위해서도 Sine 함수를 이용하였습니다.

2. Pixi.js를 이용한 배경

얘가 범인이에요…

원래 파동 애니메이션은 html의 canvas 위에서 자바스크립트 코드를 이용해 직접 그리는 방식을 이용하였습니다. 하지만, 파동과 타이머의 글씨가 블렌딩되는 UI에서 문제가 발생하였습니다. 블렌딩을 하기 위해 globalCompositeOperation() 함수를 사용하였는데 Safari와 Chrome의 결과가 서로 달랐습니다. 그래서 브라우저별로 서로 다른 블렌딩 모드를 사용하도록 구현하려고 했지만 적합한 블렌딩 모드를 찾을 수 없더군요. 그래서… 구세주 Pixi.js를 사용하여 제작하였습니다. 제가 Pixi.js를 사용한 이유는 브라우저 간 호환성이 좋다는 점과 빠르고 가볍다는 장점때문입니다. 그리고 기존의 canvas와 사용방법이 그렇게 크지 않다는 것도 장점이었기 때문에 빠르게 파동을 구현할 수 있었습니다.
Pixi.js를 사용하면서 가장 아쉬웠던 부분은 그라디언트 부분이었습니다. 파동에 그라디언트 색상이 포함되어 있었기 때문에 이를 구현하기 위해 Pixi.js에서 그라디언트를 사용해야 했지만 Pixi.js는 그라디언트를 제공하고 있지 않았습니다.
그렇기 때문에 canvas 바깥 html에서 투명 그라디언트를 가진 엘리먼트를 추가하여 파동의 그라디언트 색상을 구현하였습니다.

3. 애니메이션 큐

연진아 나 지금 되게 신나

위 사진은 wavetimer의 초기 버전입니다. 사진 상의 문제점은 비동기 요청에 의해 파동이 특정 위치로 움직이는데, 이때 비동기 요청 두 개가 동시에 들어오면, 한 요청은 파동을 위로 보낼려고 하지만 다른 한 요청은 파동을 아래로 보낼려고 하여 두 요청이 서로 엇갈리는 상태입니다. 이를 해결하기 위해 한 애니메이션이 종료되고 나서 그 뒤의 애니메이션을 처리하는 방식이 필요했기 때문에 이를 큐로 구현하였습니다.

wave-anitmate.js 中

위 파일에서 WaveAnimate 클래스는 애니메이션 요청을 정의합니다. 이 클래스에서 파동이 어디로 이동해야하는지를 정의합니다. 그리고 WaveAnimateQueue 클래스는 애니메이션 큐를 가지고 있습니다. 매 0.1초마다 현재 진행중인 애니메이션의 상태를 확인하고, 만약 애니메이션이 끝났으면 큐의 첫 번째 요소를 빼내는 형태로 구현되어 있습니다. 이와 같이 구현하여 두 애니메이션이 동시에 시행될 수 없도록 합니다.

4. 집중 타임/휴식 타임

timer-ui.js 中

타이머는 Pomodoro와 같이 집중 타임이 끝나면 휴식 타임을 가지도록 설계하였습니다. 그렇기 때문에 집중 시간이 끝나면 자동으로 휴식 타이머로 변경되도록 구현이 필요했는데요. 따라서 위 두 변수를 가지고 타이머가 완료되고 초기화될 때 현재 breakMode가 참인가 거짓인가에 따라 타이머 모드를 구분하여 타이머를 변경하는 방식으로 구현하였습니다.

5. 타이머 종료 사운드

네? 사운드 재생은 쉬운거 아니냐고요? 네 맞습니다. 맞는데...

문제는 사파리 브라우저입니다. 크롬 같은 경우 언제든지 사운드 재생을 할 수 있지만, 사파리 브라우저의 경우 사용자가 직접 웹에서 오디오를 트리거(클릭 or 터치)하는 경우에만 오디오를 재생시킬 수 있습니다. 그렇기 때문에 사실상 사용자의 트리거가 없다면 자동으로 사운드를 재생시킬 수 없습니다. reference

하지만 언제나 그랬듯이 트릭은 존재합니다.

핵심은 사용자의 트리거 여부입니다. 그렇기 때문에 사용자가 트리거 할 때 빈(Blank) 사운드를 재생시키면, 이 경우는 사용자가 직접 트리거를 한 경우이기 때문에 사운드가 재생될 것입니다. 그리고 나중에 제가 원하는 사운드를 플레이하면 되는거죠. wavetimer의 경우에도 타이머 종료 사운드를 듣기 위해서 사용자가 반드시 트리거해야하는 버튼이 있습니다. 바로 ‘start’ 버튼 이죠. 타이머 종료 사운드를 들으려면 타이머가 시작되어야 하고, 타이머가 시작되기 위해서는 사용자가 ‘start’ 버튼을 눌러야 합니다. 그렇기 때문에 사용자가 ‘start’ 버튼을 누를 때 Blank 사운드를 재생시키고, 타이머가 종료될 때는 원래대로 종료 사운드를 재생시키면 사파리에서도 종료 사운드를 들을 수 있습니다.

6. 라이트/다크 테마

테마 기능은 다행히도(?) 제가 생각한 방향 그대로 구현할 수 있었습니다. 사실 테마 기능은 디자인할 때가 가장 어려웠던 것 같네요.

주관적 후기

wavetimer 프로젝트를 시작하기 전까지만 해도 ‘이게 과연 될까?’ 라는 생각을 끊임없이 해왔었던 것 같습니다. 하지만 열번 찍어 안넘어가는 나무 없듯 끊임없이 생각하고 시도하여 마무리 지었다는 생각에 뿌듯합니다. 이번 프로젝트를 제작하며 특히 신경썼던 부분들은 다음과 같습니다.

1. 매일마다 오늘의 Todo 리스트 작성

개발을 시작하기 전 앞서 오늘 개발해야 할 기능들을 쫙 나열하여 작성하고 이를 완성할 때마다 해당 줄을 삭제하여 완료됨을 표시하였습니다. 하지만 아쉬웠던 것은 매일 새 Todo 리스트를 작성할 때 이전에 메모한 리스트를 삭제하고 새로 작성했기 때문에 이전의 리스트를 확인할 수 없었다는 점입니다. 그래서 다음 진행할 프로젝트부터는 이를 감안하여 Todo 리스트를 관리해주는 서비스를 이용해 볼 것 같네요.

2. 유연한 코드

timer-ui.js 中

작년 오목AI을 개발하고 거진 3달 만에 코드를 작성한 것이기 때문에, 코드를 작성할 때 더 생각하고 조심하게 코드를 작성한 것 같습니다. 그러다 보니 프로젝트의 구조에 대해 생각하게 되면서 나중에 어떤 변수와 함수들이 필요하겠구나를 미리 생각할 수 있었고 이를 코드에 반영할 수 있었습니다. 그리고 유연한 코드 덕분에 나중에 코드를 작성할 때도 과거에 작성한 코드들을 고칠필요 없이 순조롭게 진행할 수 있었습니다.

3. 변경사항은 수시로 깃헙에 커밋 앤 푸시

우리집 잔디

이전에 프로젝트를 진행할 때는 큰 기능을 추가했을 때만 깃헙에 푸시하였는데, 그러다 보니 기능을 추가하면서 대대적으로 삭제하거나 변경한 코드들을 한눈에 보기가 어려웠습니다. 그래서 이번 프로젝트부터는 작은 단위의 기능들을 추가할 때마다 수시로 깃헙에 푸시를 하여 이를 방지하였습니다.

4. 애플같은 애니메이션

저는 앱등이 입니다. 제가 애플 기기들을 주로 사용하는 이유는 여러가지가 있겠지만, 그 중 부드러운 애니메이션 효과도 한 요소 입니다. 애플의 애니메이션 효과들을 말로 설명하기는 어렵지만, 확실한 것은 갤럭시 디바이스와 같은 안드로이드 진영에서는 느낄 수 없는 부드러운 애니메이션을 제공한다는 점 입니다. 그리고 이를 최대한 wavetimer에서도 경험할 수 있도록 노력하였습니다. 특히, 모든 애니메이션을 진행할 때 선형(Linear)으로 애니메이션을 진행하는 것이 아닌 S-Function 형태로 애니메이션이 진행하여 사용자에게 부드러운 애니메이션 경험을 느낄 수 있도록 구현하였습니다.

프로젝트에 추가할 점

1. 백엔드 기능들

  • 로그인 및 회원가입 기능
  • Synchronized 티이머 기능
  • 유저 피드백 페이지

2. 모바일 지원

웹앱의 형태로 휴대폰이나 태블릿에서 사용할 수 있도록 제작

-

2주차 강의 복습

Comments powered by Disqus.

바로가기