/wanted-pre-onboarding

원티드 프리온보딩 프론트엔드 코스

Primary LanguageTypeScript

원티드 프리온보딩 코스




필수 구현 사항

  1. 상단 GNB(Global Nabigation Bar)
  2. 슬라이드(또는 캐러샐이라고 불림) 영역
  3. 반응형(Responsive Web) 구현
  4. Github Repository 주소와 배포 링크 제출

구현 요구사항 분석

  • 슬라이드 되어야 할 이미지가 항상 페이지의 정중앙에 유지되도록 설정
  • 슬라이드 이미지가 3~4 초마다 자동으로 슬라이드 되어야 함
  • 이미지에 마우스 hover 시에 이미지 슬라이드 멈춤.
  • 이미지가 무한 루프로 연결되어 있음
  • 브라우저 배율을 500%로 최대한 줄여도 이미지가 끊이지 않고 계속 보여야 함
  • 페이지를 새로 고침 할 때마다 나오는 이미지가 다름
    • 랜더링 할 때 이미지 데이터 배열을 랜덤으로 수정
  • 화면이 줄어들 때마다 이미지 width 값을 동적으로 변경해 줘야 함
    • 이미지가 resize 될 동안에는 애니메이션과 화면 이동을 막아야 함
  • 슬라이드 이동 버튼을 빠르게 못 누르게 하기 위해 버튼을 클릭하면 0.6초 동안 비활성화 시키고 그 후에 다시 활성화시켜야 함
  • 이미지가 어느 픽셀에서든 항상 가운데 유지해야함
  • pc, 모바일 스와이프 기능 구현
    • 마우스가 이미지 영역 밖으로 나가면 이동 되던 이미지가 원래 자리로 돌아와야함
    • 특정 길이만큼 마우스가 움직이면 그쪽 방향으로 스와이프가 되어야함

이슈 사항 && 해결 과정

슬라이드 되어야 할 이미지가 항상 페이지의 정중앙에 유지되도록 설정

처음에는 이미지 리스트 박스를 position: absolute를 이용해서 가로로 정렬을 했었습니다. 그렇게 해서 이미지가 잘 나타나고 있어서 "이미지가 잘 나왔네!" 라고 생각했습니다. 그런데 브라우저 아래에 보니 엄청난 스크롤이 생기는 걸 보고 스크롤을 넘겨보니 이미지 데이터가 다 노출되는 현상이 일어났습니다. 그렇게 상위 엘리먼트의 overflow: hidden 값을 주게 되니 이미지 박스 전부가 hidden 처리가 되어서 보이지 않았습니다.. position: absolute에 문제가 있기 때문에 position을 지우고 상위 엘리먼트에 width: 100%를 준 뒤 width: 이미지 크기; margin: 0 auto로 이미지 크기만큼 width 값을 설정 후 가운데 정렬로 해결하였습니다.

하지만 이렇게 됐을 때 두번째 이슈가 발생했습니다. 이미지의 첫번째 요소부터 가운데 정렬을 해버리니 아래 이미지의 빨간 엑스 표시 부분이 빈 공백으로 나오게 되었습니다. 고양이

이미지 데이터 리스트를 무한으로 나오게 설정하지 않았기 때문에 발생한 이슈였기 때문에 무한으로 연결하는 작업을 해주었습니다. 이미지를 무한으로 연결하기 위해서는 [...원본 이미지 리스트 복사본, ...원본 이미지 리스트, ...원본 이미지 리스트 복사본] 이렇게 배열을 생성해 놔야 자연스러운 연결이 됩니다. 그렇게 연결을 한 뒤 이미지의 최초 위치를 중앙으로 잡아주게 되면 이미지가 양옆으로도 잘 나오게 됩니다. 이미지의 최초 위치를 중앙으로 잡은 로직은 아래에 설명하겠습니다.


무한 이미지 슬라이드(캐러샐) 기능

처음에 시작할 땐 무한 이미지 슬라이드를 너무 쉽게 생각했었습니다. 단순히 이미지가 슬라이드 1칸 될 때마다 첫 번째 요소를 지우고 맨 뒤에다가 연결해 주고 이런 식으로 하면 될 줄 알았는데, 막상 해보니 첫 번째 요소를 지우게 되면 인덱스가 하나씩 밀리기 때문에 이미지가 슬라이드 하지도 않았는데 앞으로 땡겨지는 현상이 일어났습니다. 그렇게 고민을 하다가 예전에 대학교 동기들과 마켓 컬리 클론 코딩을 했었던 기억이 났습니다. 그때도 원티드 메인과 비슷한 무한 이미지 슬라이드가 있었는데, 그 당시에 했던 로직이 원본 이미지 배열을 2개 복사해서 [...원본 이미지 리스트 복사본, ...원본 이미지 리스트, ...원본 이미지 리스트 복사본] 이렇게 만든 뒤에 가운데 원본 이미지 리스트 중앙에서 시작해서 오른쪽(왼쪽)으로 이동하다가 시작했던 이미지와 같은 이미지가 나오게 되면 그다음으로 슬라이드 되는 것이 아니라 사용자 몰래 처음에 시작했던 이미지 리스트로 바꿔치기하는 로직입니다. 이렇게 하게 되면 이미지가 계속해서 무한으로 나오는거 처럼 보이게 됩니다.

여기서 또 해결해야 할 과제가 생겼습니다. 그렇다면 이 [...원본 이미지 리스트 복사본, ...원본 이미지 리스트, ...원본 이미지 리스트 복사본] 배열에서 어떻게 가운데 위치부터 시작하게 할까?였습니다. 위치를 바꿔주는 css인 transform 으로 랜더링 되자마자 가운데 위치로 이동시켜주는 방식으로 처음 구현을 하였지만 그렇게 되면 첫 랜더링 되자마자 시작 위치에서부터 가운데 위치까지 이동하는 모션이 보였습니다. 그래서 transform 처럼 움직이는 게 아니라 위치를 잡아주는 absolute를 이용하기로 했습니다. 이미지 리스트 박스 아래에 div 태그를 하나 더 만든 뒤에 position: absolute를 주어서 시작 위치값을 left: 슬라이드 될 아이템 width 값 곱하기 배열 가운데 인덱스 이렇게 해서 시작 위치를 가운데로 잡아 주었습니다.

이제 시작 위치도 이미지 배열 중앙으로 잡아주었고 캐러샐 기능만 구현하면 되었습니다. 캐러샐 기능은 쉬워서 쉽게 구현하였는데, 구현하니 또 이슈가 발생했습니다. 이미지를 무한으로 연결시켜주는 로직 부분에서 사용자 몰래 이미지를 바꿔치기한다고 했었는데, 이미지 슬라이드를 하다 보면 바꿔치기할 때 바꿔치는 장소로 슬라이드가 되돌아가는 모션이 보이게 됐습니다. 바꿔치는 순간에는 슬라이드 모션이 보이지 않게 바꿔야 하기 때문에 const [isAnimation, setIsAnimation] = useState(true) 라는 상태를 만들어서 이미지 위치를 바꿔치는 순간에만 isAnimation을 false로 만들어서 애니메이션 효과를 끄는 것으로 해결되었습니다.


버튼을 클릭하지 않아도 자동으로 이미지 슬라이드 되게 설정

원티드 페이지를 보면 양옆 버튼을 클릭하지 않아도 이미지가 3~4 초마다 자동으로 슬라이드가 되고 있습니다. 자동 슬라이드가 되는 것은 구현을 하였지만, 여기서도 문제가 발생했습니다. 원티드 페이지를 보면 이미지에 마우스를 올려놓은 상태에선 자동으로 슬라이드가 되지 않고 있는데, 제가 그 부분은 처리를 안 해줘서 마우스를 이미지에 올려놓아도 계속해서 슬라이드가 되고 있었습니다. 그래서 const [isFlowing, setIsFlowing] = useState(true) 라는 상태를 만들어서 isFlowingtrue일때만 자동 슬라이드가 되도록 설정하고 이미지에 마우스를 올렸을 땐 isFlowingfalse로 설정해주어서 자동 슬라이드가 되지 않도록 하였습니다.


반응형으로 화면이 줄어들 때 이미지 width 값이 동적으로 바뀌어야함

처음에는 반응형으로 구현할 때 이미지가 특정 픽셀마다 줄어드는 것으로 생각했습니다. 예를 들어서 화면이 1200px 일 경우에는 이미지가 1060px이고 화면이 1060px 일땐 이미지가 760px, 화면이 760px 일 땐 이미지가 500px 이런 식으로 되는 것으로 생각했습니다. 하지만 원티드 페이지를 잘 분석해 보니 화면이 1200px 아래로 줄어들게 되면 이미지가 화면에 맞게 동적으로 계속 바뀌게 되어있어서 조금 당황했습니다. window.innerWidth 값을 이용하면 브라우저의 width 값을 받아올 수 있었는데, 원티드 페이지를 잘 분석해 보니 현재 브라우저의 width 값 - 80px로 이미지의 width 값이 결정되고 있는 걸 확인했습니다. 그래서 브라우저 width값이 1200px 아래로 내려가게 되면 동적으로 이미지 width 값을 설정하도록 구현하였습니다.

반응형으로 이미지 크기를 줄이는데 까지는 잘 성공했습니다. 하지만 또 이슈가 발생했는데요, 화면을 줄이는 와중에 이미지가 슬라이드가 되면서 슬라이드로 이동하는 거리와 이미지 크기가 달라지게 되어서 UI가 이상하게 나오는 현상이 발생했습니다. 원티드 페이지를 살펴보니 화면이 줄어들거나 늘어날 때는 이미지가 슬라이드가 되지 않고 있었습니다 그래서 저도 다음 코드와 같이 화면이 resize 될 동안에는 이미지 슬라이드가 안되도록 구현하였습니다.

useEffect(() => {
  const handleResize = () => {
    // resize 되는 동안에는 애니매이션, 이동을 막아줌
    setIsAnimaion(false);
    setIsFlowing(false);
    // resize 가 끝난 0.5초 이후에 다시 활성화
    setTimeout(() => {
      setIsFlowing(true);
      setIsAnimaion(true);
    }, 500);
  };
  window.addEventListener("resize", handleResize);
  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, []);

슬라이드 이동 버튼을 빠르게 클릭하면 애니메이션이 엄청 빠르게 이동되는 현상이 생김

테스트를 하던 와중 슬라이드 이동 버튼을 연속해서 빠르게 클릭하면 클릭 한 횟수만큼 애니매이션이 빠르게 움직이는 현상이 발생했습니다. 원티드 페이지는 이걸 어떻게 해결했는지 확인을 해보니 버튼을 클릭하면 0.6초 정도 버튼을 비활성화 시킨 후에 다시 활성화시키는 방식으로 구현되어 있었습니다. 그래서 저도 아래 코드와 같이 0.6초 정도 비활성화 시키고 다시 활성화시키는 방식으로 구현하였습니다.

const onDisabledButton = () => {
  setIsDisabled(true);
  timer.current = setTimeout(() => {
    setIsDisabled(false);
  }, 600);
};

useEffect(() => {
  return () => {
    if (timer.current) {
      clearTimeout(timer.current);
    }
  };
}, [timer]);

pc, 모바일 스와이프 기능 구현 과정

스와이프 기능 지금까지 구현해왔던 코드들을 토대로 조합하는 식으로 구현하면 되어서 다소 쉽게 진행됐습니다. 하지만 그럼에도 고려해야 될 부분들이 조금 있어서 고민했던 내용들과 구현했던 과정에 대해서 남겨보려고 합니다. 스와이프 기능은 크게 모바일 스크린 터치 이벤트, 피씨 마우스 클릭 이벤트 이렇게 두 가지로 나눠서 구현했습니다. 클릭(터치)한 순간의 px 값으로 이미지 배열들을 움직이게 해주고, 클릭(터치)이 완료된 순간에 px 값을 통해서 움직인 거리에 따라 스크린을 이동 시키 도록 구현하였습니다.

이때 onMouse 이벤트와 onTouch 이벤트의 차이에서 발생하는 이슈가 있었습니다. onTouch 이벤트의 경우에는 영역 밖에서 터치를 취소하게 돼도 onTouchEnd 이벤트가 실행이 되었는데, onMouse이벤트는 해당 영역 내에서 마우스를 취소할 경우에만 onMouseUp 이벤트가 실행되었습니다. 그래서 onMouse 이벤트에서는 onMouseOut 이벤트를 추가적으로 실행하여 영역 밖으로 나가게 되면 이벤트를 취소시키도록 설정하였습니다.

이미지를 스와이프 할 때 반응속도가 느리게 움직이는 이슈도 있었습니다. 개발자 도구를 통해서 이미지를 스와이프 할 때 이미지의 px이 잘 작동하는지 체크해보았는데, 너무 잘 작동하고 있었고, useState의 상태가 잘 변화하는지도 체크해봤는데 잘 작동하고 있었습니다. 그래서 원인이 뭐지..? 하고 계속 보다가 아! 지금 이미지에 애니메이션이 걸려 있었구나 하고 생각이 났습니다. 그래서 이미지 스와이프가 시작되면 애니메이션을 잠깐 끄고 스와이프가 완료되면 다시 애니메이션을 실행하는 식으로 수정하여서 해당 이슈를 해결하였습니다.

스와이프 기능의 마지막 고민으로는 어느 정도 스와이프를 해야 해당 이미지를 슬라이드 시켜줄지에 관한 고민이었습니다. 원티드 홈페이지에서 분석을 해봐도 정확히 어느 정도 스와이프 해야 슬라이드가 되는지 잘 모르겠어서, 이 부분은 제가 아래 코드와 같이 짐작으로 추정하였습니다. 마우스를 조금 스와이프 했을 때는 다시 원상태로 돌아오고, 이미지 크기의 5분의 1 이상 스와이프 하게 되면 그쪽 방향으로 슬라이드가 되도록 기준을 잡았습니다.

// 스와이프 이동 기준
const moveRange = useMemo(() => {
  return Math.floor(slideItemWidth / 5);
}, [slideItemWidth]);

이동 범위가 스와이프 이동 기준보다 크면 슬라이드를 이동 시키도록 하는 코드

if (touchMoveDistance > moveRange) onPrevSlide();
if (touchMoveDistance < moveRange * -1) onNextSlide();

해결하지 못한 과제

  • 개발자 도구에서 메인 로고의 폰트를 결국 못찾아서 최대한 비슷한 roboto 폰트를 이용했습니다 ..