본문 바로가기

Frontend Study

[FE_Bootcamp] Main Project_Home

메인 프로젝트의 개요를 전부 잡고, 본격적으로 개발에 들어가게 되었다. 

개요를 대강 설명하자면, '위시리스트 기능이 포함된 가계부'로, 크게 5개의 페이지로 나눌 수 있다.

  • 가계부
  • 소비 패턴 분석
  • 위시리스트
  • 유료 구독 결제
  • 마이페이지

여기에 홈 화면과 로그인/회원가입까지.

 

프론트엔드 팀원은 3명이었으므로, 한명씩 만들고 싶은 페이지를 가져간 뒤, JIRA를 통해 작업 상황을 공유하고 맡은 작업이 끝나면 다음 작업을 가져오는 방법을 사용했다.

 

 

우선 개발 첫날이었으므로, 서버 통신이 딱히 필요하지 않은 홈 화면을 만들기로 하였다.

 

개발 과정은 전부 쓰지 않고, 내가 처음 써보았거나 공부가 좀더 필요해서 많이 알아보고 썼던 기능 또는 라이브러리 등에 대해서만 쓸 예정이다.


1. 화살표를 클릭하면 자동 스크롤 하기

메인 페이지

 

메인 화면에서, 맨 아래 화살표를 누르면 자동으로 다음 화면으로 넘어가게 만들고자 했다. 

이것저것 찾아본 결과, JS의 내장 메서드인 ScrollIntoView() 메서드를 사용하면 좋을 것 같아 관련 내용을 찾아보았다.

 

ScrollIntoView는 해당 요소가 포함된 컨테이너, 즉 상위 요소를 통째로 스크롤하는 기능을 가지고 있다.

 

const onMoveBox = () => {
    element.current.scrollIntoView({ behavior: "smooth", block: "start" });
};

<Background>
    .
    .
    .
    <ScrollArrow
        src='https://www.svgrepo.com/show/334629/down-arrow-square.svg'
        onClick={onMoveBox}
        isView={windowSize > 500}
    >
    .
    .
    .
</Background>
<SummaryContainer ref={element}>
    .
    .
    .
</SummaryContainer>

 

ScrollArrow라는 이미지를 클릭하면, OnMoveBox가 실행되며 스크롤이 자동 실행된다.

element는 스크롤을 실행시킬 요소를 의미하는데, 그냥 element.scrollIntoView를 입력하니 에러가 발생했다.

우선 이 element가 어떻게 생겼는지 확인을 해보고, 이에 맞는 해결책을 찾기로 했다.

 

console.log(element)의 결과

 

element는 ref를 통해 지정한 DOM 요소로, 객체의 모양을 가지고 있었다. 

객체의 current 프로퍼티가 div를 가리키고 있는데, 이 div = 우리가 스크롤 해서 보여주고 싶은 div인지 파악만 하면 될 것 같다.

 

 

React는 따로 클래스네임을 설정하지 않으면 저렇게 임의의 문자로 클래스명이 지정되는데, 그래서 찾기 조금 힘든 점도 있는 것 같다.

여튼 이동시킬 div와 같은 것을 확인했으니, element.scrollIntoView가 아니라 element.current.scrollIntoView로 코드를 고쳐주었다.

 

매개변수로는 어떻게 이동을 진행할지에 대한 behavior, 상하 이동 위치에 대한 block, 좌우 이동 위치에 대한 inline 등이 있다. 우리는 상하 스크롤이므로 block을 start로 지정해주고, 부드럽게 이동시키기 위해 behavior를 smooth로 지정해준다.

 

완료 화면.


2. 가려져 있다가 스크롤을 내리면 요소가 나타나게 하기

빈 화면에서, 스크롤을 내리면 요소가 하나하나 등장하게 만들고자 했다.

opacity를 0으로 지정했다가, 화면에 보이면 1로 만드는 방법을 사용하면 될 것 같았는데, 문제는 화면에 보이는지를 어떻게 판단하냐였다. 

이는 react-intersection-observer의 useInView Hook을 통해 쉽게 구현 할 수 있었다.

 

react-intersection-observer는 리액트 라이브러리로, 따로 설치를 통해 사용해야 했다.

 

npm install react-intersection-observer

 

그리고 나서는, import를 통해 useInview Hook을 불러와준다.

 

import { useInView } from "react-intersection-observer"

 

useInview는 useEffect처럼 2개의 파라미터를 갖는다.

 

const [화면에 나오는지 판단할 요소, 화면에 요소가 나왔는지에 대한 Boolean 값을 담은 변수] = useInview();
//ex
const [ref, inView] = useInview()

 

useInview를 사용하기 위해서는 ref로 화면에 보일 요소를 지정해줘야 하고, 이 요소가 화면에 보였다면 반환하는 true나 false를 담을 변수를 또 설정해줘야 한다,

 

위 예제에서, ref가 화면에 나타났다면, inView는 true가 되고, 반대로 화면에서 사라진다면 false가 된다.

 

const [element, inView] = useInView()
<Container align='left' ref={element} isView={inView}>
    .
    .
    .
</Container>

 

화면에 나타낼 요소일 Container를 element라는 ref로 지정해두고, isView props는 보였는지에 대한 boolean 값을 담은 inView로 지정해준다.

 

const Container = styled.div`
  font-family: 'Pretendard', sans-serif;
  display: flex;
  flex-direction: row;
  margin: 90px;
  justify-content: ${props => props.align === 'left' ? 'flex-start' : 'flex-end'};
  animation: ${props => props.isView ? 'appearText 2.5s' : ''};
  opacity: 0;
`
@keyframes appearText{
  0% {opacity: 0;}
  100%{opacity: 1;}
}

 

보다시피 Container의 opacity는 기본 0이지만, 만약 isView props가 true라면 2.5초간 appearText 애니메이션을 실행한다.

appearText는 opacity를 0에서 1로 만들어주기 때문에, 결론적으로 inView가 true로 바뀌는 순간 Container는 2.5초동안 opacity 0에서 1로 바뀌면서 화면에 나타나는 것이다. 

 

화면이 바뀔 때 마다 계속 사라졌다 등장했다 한다

 

이정도에서 만족해도 좋지만, 화면을 올렸다 내렸다 할 때 마다 사라지는게 보기 불편해서, 일단 한번 나타나면 계속 나타나 있는 상태를 유지하고자 했다.

 

일단 inView가 true로 바뀌면, 다시 false로 바뀌지 않게 만들어야 하는데 그러기 위해서는 inView가 아닌 다른 상태가 필요했다. 

이럴 때 가장 쓰기 좋은 것은 useState이므로, useState로 새로운 상태를 만들어주고, 만약 inView가 true라면 해당 상태를 true로 바꿔준 뒤 다시 false로 돌아오지 않게 해주면 됐다.

 

const [isView, setIsView] = useState(false)
  if(inView){
    setIsView(true)
  }

 

그런데 이렇게 쓰니 Too many rerender 오류가 발생했다. 

이러한 상황은 주로 조건문 내에서 상태를 변경하는 경우 발생하는데, if(inView) 문 안에서 setIsView(true)를 호출하게 되면, setIsView가 호출되면서 상태가 업데이트되고 컴포넌트가 다시 렌더링되는데, 이때 inView는 계속 변함없이 참인 상태라 계속해서 렌더링이 일어나는 것이다.

 

이를 방지하기 위해 useEffect를 써주었다.

 

useEffect(() => {
    if(inView){
      setIsView(true)
    }
}, [inView])

 

마지막으로, 애니메이션이 지속되는 2.5초가 끝나면 다시 opacity가 0으로 돌아가는 문제가 발생했다.

이를 해결하기 위해서는 애니메이션의 마지막 상태를 계속 유지하는 코드가 필요했다.

 

animation-fill-mode: forwards;

 

Container에 다음과 같은 코드를 추가해주면 애니메이션을 끝까지 유지할 수 있게 된다.

 

완료 화면


이 모든 과정을 거쳐 완성된 홈 화면은 다음과 같다.