[TIL][UFO] - 무한 스크롤 적용기

11199 단어 TILTIL

😓 무한스크롤

자유 게시판 페이지

초기 프로젝트를 디자이너와 설계할때 자유 게시판에서 페이지 이동을 페이지네이션으로 설정했다. 이유는 디자인적으로 데스크톱에서 게시판 리스트 페이지가 다소 비어볼일 수 있고 이를 UI요소를 넣어 해결자는 의견이 많았다.

그렇다면 무한 스크롤은 왜 적용해야 했나?

프로젝트의 윤각이 잡히기 시작했고 우리는 버전1를 만들어 배포하고 사용자 피드백을 받아보자 결정했다.

facebook 광고를 이용해 해외 대학교에 재학중인 유학생들에게 홍보를 진행했고 google analytics를 활용해서 서비스 이용 데이터를 수집했다.

결과는 우리 예상과 달랐다 😧

초기에 사용자 즉, 유학생들이 어떤 기기로 우리 서비스를 이용할까 고민할때 대학생들이기 때문에 데스크톱으로 접근하는 사용자가 더 많을꺼라고 기대했다.

하지만 google analytic로 확인한 결과 모바일로 서비스를 이용한 사람이 2.5배 많았고 실제 피드백에서 모바일 환경에서 서비스를 사용하는데 불편함이 있다는 의견을 받았다...

어찌보면 당연한 결과였다. 우리는 모바일 사용자를 위해서 반응형으로 view를 만들었지만 말그대로 사이즈만 반응형을 줄었다 커졌다 했을뿐 모바일 사용자 입장에서는 불편함을 느끼는게 당연했다.

우리는 직접 모바일에서 서비스를 테스트했고 페이지네이션의 버튼을 클릭한다거나 게시물 작성을 위해서 글쓰기 버튼을 누르는 행동이 모바일에서는 정말 불편했다.


🏋️‍ Challenge

이를 해결하기 위해서 모바일 환경에서는 페이지 이동없이 무한 스크롤을 적용해서 게시물을 불러오게 변경했다.
(기능 구현을 마치고 막상 글로 작성하려니 너무나 간단한 작업인것으로 보이지만...정말 힘들었다.)

1. 무한 스크롤 만들기

무한 스크롤을 만드는 방법은 10개의 레프런스를 찾으면 10개가 다 다른 방식으로 구현을 하고있어서 구현 방식을 정하는데도 시간을 많이 소모했다.

첫번째로 고려했던 방식은 IntersectionObserver API를 활용하는거였다.

IntersectionObserver가 무엇인지는 MDN 문서에 나와있는데...

IntersectionObserver(교차 관찰자 API)는 타겟 엘레멘트와 타겟의 부모 혹은 상위 엘레멘트의 뷰포트가 교차되는 부분을 비동기적으로 관찰하는 API이다.

라고한다.

간단하게 풀어보면 Intersection Observer 란 화면(뷰포트) 상에 내가 지정한 타겟 엘레멘트가 보이고 있는지를 관찰하는 API이다.

무한 스크롤을 Intersection Observer 적용해서 만든다면 특정 타켓 요소 useRef()를 통해서 관찰하게 만들고 해당 요소가 view port 즉, 화면에 나타나면 서버에 추가적으로 게시글을 불러와 게시글 리스트 배열에 추가해주면 되는거 같았다.

특히 무한 스크롤 구현에 가장 대표적인 방법으로 window scroll을 활용할때 불필요한 요청을 줄이려고 debounce, throttle 적용하는데 이를 사용하지 않아도 된다는 장점이 있었다.

스크롤 이벤트에서는 현재의 높이 값을 알기 위해offsetTop 을 사용하는데 정확한 값을 가져오기 위해 매번 layout을 새로 그리게 된다.

위의 자료에서 볼 수 있듯이 layout을 새로 그린다는 것은 렌더 트리를 재생성한다는 뜻인데, reflow라고도 불리우는 이 일련의 과정이 반복되면 당연히 브라우저의 성능이 저하되고 화면의 버벅거림이 생길 수 밖에 없다.


두번째로 고려했던 방식은 이미 몇번 구현해본 무한 스크롤 컴포넌트를 만들어 정보를 담은 컴포넌트를 감싸주는 방식이다.

Intersection Observer를 활용한 방법도 분명 매력적이라고 느꼈지만 빠르게 기능을 구현해서 재배포 해야하는 상황이였기 때문에 기존에 해왔던 방식으로 진행하고 Intersection Observer API는 추후에 개인 프로젝트에서 다루기로 결정했다.

const InfinityScroll = props => {
    const { nextCall, children, is_loading, is_next, size } = props;

    const throttle = _.throttle(() => {
        let scrollHeight = document.documentElement.scrollHeight;
        let innerHeight = window.innerHeight;
        let scrollTop =
            (document.documentElement && document.documentElement.scrollTop) ||
            document.body.scrollTop;
        let current_height = scrollHeight - innerHeight - scrollTop;

        if (current_height < size) {
            if (is_loading) {
                return;
            }

            nextCall();
        }
    }, size);
    const throttle_callback = useCallback(throttle, [is_loading]);

    useEffect(() => {
        if (is_loading) {
            return;
        }
        if (is_next) {
            window.addEventListener("scroll", throttle_callback);
        } else {
            window.removeEventListener("scroll", throttle_callback);
        }

        return () => window.removeEventListener("scroll", throttle_callback);
    }, [is_loading, is_next]);
    return <>{children}</>;
};

무한 스크롤 컴포넌트이다. 해당 컴포넌트는 5개의 props를 전달 받아 사용한다.

  • nextCall
  • children
  • is_loading
  • is_next
  • size

nextCall은 스크롤이 바닥에 바닥에 닿았을 떄 실행되는 함수이며 리스트를 불러오는 api 요청을 하는 콜백을 전달받는다.

is_loading 값을 통해 비동기 요청이 진행중일때 nextCall이 다시 실행되지 못하게 관리했다.

is_next는 다음 게시물 페이지가 존재하는지 확인하는 값이다.

size는 게시물을 몇개씩 불러올지 정하는 값이다.


자유 게시판 페이지에서 무한 스크롤을 적용한 코드이다.

<InfinityScroll
nextCall={nextCall}
is_next={nextPage <= totalPage ? true : false}
size={100}
is_loading={is_Loading}
>
<BoardBox
postList={freeBoardPostList && freeBoardPostList}
preview={true}
boardName="freeboard"
/>
</InfinityScroll>

좋은 웹페이지 즐겨찾기