[팀프로젝트] JS || '한다면' 모의 면접 웹 앱

jjyung·2021년 11월 6일
6

Project

목록 보기
1/1
post-thumbnail

'한다면' 모의 면접 웹 앱 with JS/HTML/CSS

저희 '한다면' 서비스는 취준생이 어떠한 기업이든 원하는 기업에 다 갈수 있도록 각 취준생에게 알맞는 모의 면접 서비스를 제공하는 사이트입니다. 아직 배포전이지만, 수정 후 배포할 예정입니다.

기간

  • 2021.10.31(일) : 각 사이트 서비스 구성 및 기획, Figma 시안 완성, 칸반차트 생성
  • 2021.11.01(월) : 깃 레포 생성, 공통 CSS, UI&UX 제작, 개인 마크업과 CSS 제작
  • 2021.11.02~11.02(화~수) : 개별 JS 작업
  • 2021.11.03(목) : 개별 JS 작업, 코드 리뷰 및 리펙토링, 발표 위한 저녁 회의
  • 2021.11.04(금) : 발표 준비 및 발표

사용 스택

  • JS
  • Git(git flow)
  • SCSS
  • HTML
  • Figma

주제 선정 배경

  • 코로나로 인해 면접의 방식이 크게 바뀌어, 1차 인터뷰는 대부분 비대면 면접으로 바뀌게 되었습니다. 이에 따라 모의 면접 컨텐츠가 증가하고 있지만 직무 면접 관련 모의 면접이 부족하다고 생각했습니다. 또한, 취준생을 위해 무료이면서 실제 면접 상황을 대비하기 위한 앱이 부족하다고 생각하여 이 서비스를 기획하게 되었습니다.
  • 반응형으로 만들어 모바일가 웹 모두 면접을 연습할 수 있도록 기획했습니다.
  • 우리가 취준생이기도하고 많은 취준생들에게 필요한 서비스를 구현하고 싶었습니다. 아직 배포는 하지 못했지만 리펙토링을 거쳐 서비스를 배포할 예정입니다.
  • 배포 이전에 면접을 앞둔 우리 반 사람들에게 사용하도록 할 예정입니다..ㅎㅎ

서비스 흐름

메인 페이지 -> 면접 환경 설정 페이지 -> 면접 안내 사항 확인 -> 면접 진행 페이지 -> 면접 레포트 페이지

메인 페이지

  • 모의 면접 버튼 : 버튼을 통해 모의 면접 페이지로 이동이 가능합니다.
  • 질문 작성 버튼 : 모의 면접에 들어가기에 앞서 원하는 면접 질문을 넣어 면접 연습을 할 수 있습니다.
  • 채용 일정 : 사람인의 채용 공고 API를 사용해 채용 일정을 제공해주며 더 다양한 면접 기회에 노출시켜주고자 했습니다.
  • 최신 뉴스 : 구글 뉴스 API를 사용해 최신 뉴스를 제공해주며 기업 면접에서 뉴스 관련 질문이 나온다면, 당황하지않고 답변할 수 있도록 구현했습니다.

면접 질문 작성 페이지

  • 원하는 면접 질문을 개행을 통해 여러개 입력하여 추가한 후 면접하기 버튼을 클릭하면 입력한 질문들을 토대로 면접을 볼 수 있도록 구현했습니다.

면접 환경 설정 페이지

  • 면접 카테고리: 원하는 면접 종류를 선택해 해당 카테고리에 해당하는 질문이 랜덤하게 나오게 됩니다.
  • 질문 개수 : 원하는 질문 개수를 입력할 수 있습니다. 최대 20개까지 입력이 가능하고 만약 20이 넘는 숫자나, 숫자가 아닌것을 입력하게된다면 오류 메세지가 뜨게 됩니다.
  • 질문 답변 시간 : 원하는 질문 답변 시간을 선택해 모의 면접때 타이머를 설정할 수 있습니다.
  • 면접 환경 설정 : 오디오 녹음과 카메라를 환경설정할 수 있도록 제공해주는 페이지입니다. 카메라를 허용한다면 실제 본인 얼굴이 나오게되고, 자신의 얼굴을 보며 연습을 할 수 있도록 했습니다. 또한, 오디오도 녹음하여 결과 페이지에서 데이터를 받아올 수 있도록 했습니다. (canvas, audio, video API 사용)

면접 진행 페이지

  • 면접 환경 설정이 끝나면 면접 진행에 앞서 모달창에 숙지사항을 10초동안 보여준 후 면접이 진행됩니다.
  • 면접이 시작되면 자동으로 음성이 녹음되게되고, 화면에 얼굴도 같이 보이게되며 진짜 면접 상황이 셋팅됩니다.
  • 10초 이전에 다시 시작 버튼을 누르면 타이머와 함께 음성 녹음 파일이 리셋됩니다.
  • 문제에 답변이 끝이나면 답변 제출을 클릭해 다음 페이지로 넘어갈 수 있고, 만약 시간이 초과된다면 다음 문제로 자동으로 넘어가게됩니다.
  • 마지막 문제인지 확인한 후 마지막 문제라면 결과 레포트 페이지로 넘어가게됩니다.

면접 레포트 페이지

  • 면접 레포트 페이지에서는 면접 카테고리, 총 진행 시간, 질문당 평균 답변 시간 등 동적으로 데이터를 서버에서 가지고와 전체적인 결과를 보여줍니다.
  • chart.js를 사용해 질문별 평균 시간 차트를 보여주었습니다. 본인이 선택했던 시간에 비해 진짜 답변시간의 길이를 보여주며 실제 면접 상황때 어떻게 시간을 배분해야할지 도와주고자 해당 차트를 넣어주었습니다.
  • 어떤 질문에 대답을 했는지, 질문 리스트와함께 앞서 녹음된 파일을 서버에서 가지고와 들을수도 있고 다운받을수도 있도록 했습니다.
  • 페이지가 밑으로 많이 내려가는 상황을 대비해 페이지 상단으로 올라가는 버튼을 일정 페이지를 넘어가는 순간 생기도록 했고, 해당 버튼을 누르면 페이지 상단으로 스무스하게 올라가게 됩니다.

구현한 페이지 상세 기술 설명

chart.js API 사용 :

  1. createChart라는 함수를 만들어 함수 내부에 여러개의 객체를 만들어 차트 옵션을 설정해주었습니다. 가장 먼저 labels라는 동적으로 사용자가 입력한 질문 수를 받아와 배열의 길이로 활용했고, 데이터 x축에 번호를 적어주었습니다.
const labels = Array.from({ length: state.interviewTimeList.length }).map((_, i) => `${i + 1}`);
  1. data라는 객체를 만들어 질문 답변 시간이 배열을 동적으로 받아와 차트에 입력해주었습니다. 또한 이 객체 내부에 차트의 배경색상, 선 색상, 바 차트 배열일 경우 바의 두께, 라인 차트의 경우는 배경을 채울것인가 여부 등을 설정해주었습니다.
const data = {
    labels,
    datasets: [
      {
        label: '질문별 평균 답변 시간',
        data: state.interviewTimeList,
        backgroundColor: [
          'rgba(255, 99, 132, 0.2)',
        ],
        borderColor: [
          'rgb(255, 99, 132)',
        ],
        barThickness: 40,
        borderWidth: 1,
        fill: true,
        tension: 0.2,
      },
    ],
  };
  1. 바 차트를 위한 config와 라인 차트를 위한 config 객체를 각각 만들어주었습니다. config에는 각 차트에 필요한 옵션을 넣어주었는데, min은 0, max라는 사용자가 선택한 답변 시간을 받아와 y축에 값을 입력했습니다. 또한, 처음에 데이터를 딱 한번만 받아와도 되기 때문에 createChart 함수 내부에 화살표함수를 통해서 차트를 생성해 돔 노드에 넣어주었습니다.
  const configLine = {
    type: 'line',
    data,
    options: {
      scales: {
        y: {
          min: 0,
          max: state.selectedTime,
          ticks: {
            callback(value) {
              return value + '초';
            },
          },
        },
      },
    },
  };

  (() => new Chart(document.querySelector('.bar-chart'), configBar))();
  (() => new Chart(document.querySelector('.line-chart'), configLine))();
  1. 토글 버튼을 통해서 차트를 동적으로 보여주도록 했습니다. 그리고 버튼 색상도 바꾸어주며 현재 어떤 차트를 보고있는지도 보여주었습니다
$displayBarBtn.onclick = e => {
  e.target.style.background = '#605cff';
  $displayLineBtn.style.background = '#c9c9c9';
  $barChart.classList.add('active');
  $lineChart.classList.remove('active');
};

render 함수

  1. 해당 함수는 렌더링을 위한 함수로 화면이 그려질때 필요한 함수를 다 호출해주었고, 돔 노드를 불러와 innerHTML을 사용해 동적인 값을 넣어주었습니다.

  2. 서버에서 오디오 파일을 가져오기 위해 8비트의 iterable한 객체로 만들고, 블롭으로 변환한 후, 블롭 객체를 다운로드가 가능한 URL.createObjectURL을 통해서 url로 바꾸어주었습니다. 이렇게 바꾸어준 url을 a태그의 href에 넣어주며 다운로드가 가능하게 해주었습니다.

const url = URL.createObjectURL(new Blob([new Uint8Array(audio.split(','))]));

<a class="download" href="${url}" download="${question}.wav" title="download audio"> </a>

Await, Async, Axios

  1. 아무래도 서버에서 많은 데이터를 받아올때 동기 처리를 하면 다른 작업들을 수행하지 못해 시간이 오래 걸리기때문에 비동기 처리를 해주어야 합니다. 그래서 async와 await을 통해 비동기 처리를 했는데 단순히 프로미스가 아닌 await, async를 사용한 이유는 후속 처리 없이도 마치 동기처럼 프로미스를 사용할 수 있기때문입니다. 즉, 실행 흐름속도를 쉽게 파악할 수 있기 때문입니다.
  2. axios 라이브러리를 사용했는데, fetch api가 아닌 axios를 사용한 이유는 fetch보다 더 많은 브라우저에 지원이되고, 자동으로 JSON 데이터 파일 형식으로 바꾸어주다보니 훨씬 가독성도 좋고 코드 길이가 짧아지기 때문입니다. (물론 다운로드해야한다는 귀찮음이 존재하지만 코드 길이가 짧아질 수만 있다면...ㅎㅎ)
  3. 만약 앞서 사용자가 면접을 보지않고 바로 결과페이지로 넘어온다면 접근을 막아 다시 메인 페이지로 갈 수 있도록 했습니다
window.addEventListener('DOMContentLoaded', async () => {
  const { data } = await axios.get(router.interview, { maxBodyLength: Infinity });
  setState(data);
  if (state.questionList.length === 0) window.location.replace('/');
});

Throttle

  1. 페이지 상단으로 이동할 수 있는 버튼을 만들어 y축으로 300이상이 넘어가면 버튼이 생겨 상단으로 바로 이동할 수 있도록 했습니다. 이 때 스로틀을 사용한이유는 일정 주기마다 이벤트가 1번만 일어나도록 하여 과도한 이벤트 호출을 방지하고자 했습니다.
window.onscroll = _.throttle(() => {
  window.pageYOffset > SCROLL_DOWN_PAGE_Y ? ($scrollUp.style.display = 'block') : ($scrollUp.style.display = 'none');
}, THROTTLE_DELAY);

느낀점 및 배운점

  1. 서버에서 데이터를 많이 받으며 동적으로 대부분 구현했는데, 정말 백엔드의 필요성을 느꼈습니다. 정말 데이터 다루는 부분, 특히 오디오 데이터 다루는 부분이 너무 어려워 정말 공부를 많이 했습니다. 이 오디오와 관련해서는(Web Audio API) 따로 블로그를 적을 예정입니다.
  2. 아무래도 서버에서 데이터를 가지고와 처리하다보니 비동기의 필요성을 정말 많이 느꼈습니다. 초반에 음성 데이터를 가지고오는데 30초의 음성파일이 오는데 로딩 시간이 정말 몇 초가 걸렸습니다... ㅠㅠ 이걸 동기로 처리했다면,,,정말 아무것도 못하지않았을까..라는 생각을 하며 비동기에 대한 확실한 이해를 했습니다.
  3. 매일 오전마다 팀원들과 개발 상황을 공유하고 서로의 문제점을 공유하며 문제를 해결해나갔습니다. 특히, 오디오 파일을 어떻게 넘겨줄것인지에 대해서 상당히 많이 이야기를 나누었는데 토의를 통한 성장이 느껴저 정말 재미있었습니다.
  4. 하루동안 디스코드 화면 공유를 통해 코드 리뷰를 팀원들과 진행했는데, 정말 유의미한 시간이었습니다. 덕분에 저의 코드 퀄리티를 높일 수 있는 시간이자 코드에 대한 아이디어를 얻을 수 있었습니다.

리펙토링

  1. 차트 함수를 따로 파일을 파서 옮겼어야 하는데 데이터를 동적으로 받아오는것에 대한 처리때문에 아직 옮기지 못했습니다. 이 부분을 리펙토링을 통해 파일을 분리할 예정입니다.
  2. 버튼 토글 버튼 구현 부분에 코드의 중복이 심합니다.물론 가독성 측면에서는 이렇게 구현하는 부분이 더 좋다고 생각하지만, 코드 중복이 있다보니 코드의 길이가 길어졌습니다. 이 부분은 forEach를 통해 선택되는 element에 onclick을 하여 토글 버튼기능과 버튼 배경 색상 변경을 할 예정입니다.

결론

  • API를 사용해보기 이전에는 막연한 두려움이 있었는데, 실제로 사용해보니 생각보다 어렵지 않고 공식문서만 잘 읽으면 생각보다 쉽게 구현이 되었습니다. 또한, 토큰을 받지 않아도 활용할 수 있는 공개 API가 정말 많다는것을 깨달았고 한 번씩 사용해보며 기능을 구현해보고싶다는 생각을 했습니다.
  • 진짜 서버에서 데이터를 받아올 때, 녹음된 음원과 그 길이를 구하는 부분에서 정말 힘들었습니다... 그 덕분에 BLOB, arraybuffer, createObjURL을 공부할 수 있어 좋았습니다.
  • 직접 프로미스를 사용해보면서 서버에서 데이터를 가져오는게 얼마나 무거운 작업인지 깨달았습니다. 또한, await, async를 사용한덕분에 데이터가 가져오기 이전 상황을 아예 차단하며 undefined를 화면에 출력하는 상황을 방지했고, 원하는 데이터를 잘 받아와 화면에 보여줄 수 있었습니다. 그리고 이 기회를 토대로 비동기 처리의 필요성을 크게 느꼈습니다.

정말 재미있는 프로젝트였고, 배운것도 많아 뿌듯한 프로젝트가 되었던 것 같습니다. 심지어 팀웍도 좋아 더 즐거웠던 것 같습니다. 우리 2 기적이조 고마워요...!!!!

profile
🏃‍♀️movin' forward, developer

0개의 댓글