[웹 성능 최적화] #2. 캐러셀 (슬라이드)

김현조·2022년 11월 20일
1

웹 성능 최적화

목록 보기
3/3

캐러셀 직접 구현하기

react-material-ui-carousel 라이브러리를 사용해 넣어놓았던 메인페이지, 문제 목록 페이지의 캐러셀을 직접 구현하기로 했습니다. 이유는 다음과 같습니다.

  1. 최적화를 하기로 하며 결정한 사항인만큼 번들 사이즈를 줄이려는 목적이 있었습니다.
  2. 첫 로딩시에 캐러셀 내용이 퍼지듯이 렌더링되었는데 이게 버그로 보이는 경우가 많았습니다.
  3. 슬라이드 애니메이션을 커스텀하고 싶었습니다.
  4. 그냥 한번 직접 구현해보고 싶었습니다.

결과물

캐러셀

예쁩니다! 기분이 좋습니다!

과정

이미 잘 구현해놓으신 분들이 많아 참고자료를 드립니다.

이슈

애니메이션

슬라이드 애니메이션을 넣으니 마지막 idx -> 첫번째 idx로 이동할 때 이어지는 것처럼 보이지 않았습니다. 한번에 x축이 + 100%씩 이동하다가 갑자기 -200% 이동했기 때문입니다.
-> 눈속임으로 마지막에 첫번째 내용과 동일한 슬라이드를 하나 추가하고, 마지막 슬라이드로 넘어가면 잠깐 transition을 멈춰놓고 몰래 첫번째 슬라이드로 바꿔치기한 후 다시 transition을 넣어주면 됩니다. 참 쉽죠?

아래와 같이 현재 슬라이드 인덱스, transition 들어갔는지 여부 두가지 상태를 가지고 구현해볼 수 있습니다.

  const [currIdx, setCurrIdx] = useState(1);
  const [transition, setTransition] = useState(true);

다음 슬라이드로 넘어갈때 setCurrIdx를 호출해 인덱스를 설정해줍니다. 범위를 넘어갔을 때의 예외처리와 함께 앞서 말씀드린 transition on/off 로직을 구현하면 됩니다. 여기서 replaceSlide가 해당 로직인데 아래에 분리해놓았습니다.

  const moveSlide = (offset: number) => {
    let nextIdx = currIdx + offset;
    setCurrIdx(nextIdx);
    if (nextIdx <= 0) {
      nextIdx = items.current.length - 1;
      replaceSlide(nextIdx);
    } else if (nextIdx >= items.current.length - 1) {
      nextIdx = 0;
      replaceSlide(nextIdx);
    }
    setTransition(true);
  };

일단 슬라이드를 옮기고 나서 실제로 바꾸고자 하는 위치로 몰래 바꿔치기 하겠다는 뜻입니다. (ex. 마지막 슬라이드에 도착했다면 0번으로 바꾸겠다.)

  const replaceSlide = (idx: number) => {
    setTimeout(() => {
      setCurrIdx(idx);
      setTransition(false);
    }, 700);
  };

자동으로 넘기기 (Interval)

저는 사실 setInterval을 좀 무서워하는 편입니다. 예상치못한 사이드이펙트가 발생할 여지가 높기 때문인데요, 써야할 때는 또 써야하므로 이번 기회에 써봤습니다. 그런데 react에서는 더더욱 신경써야하는 부분이 있습니다. 바로 state가 바뀌면 App도 리렌더링이 된다는 점입니다. 따라서 setInterval이 무한루프에 빠지게 됩니다. 이것을 해결하기 위해 Dan Abramov님의 멋진 글을 읽어볼 수 있으며 결론은 다음과 같은 custom hook입니다.

import React, { useState, useEffect, useRef } from 'react';

function useInterval(callback, delay) {
  const savedCallback = useRef();

  // Remember the latest callback.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

callback 데이터가 바뀔 때마다 useEffect()가 실행되어 savedCallback의 current 값이 새로운 callback 데이터로 업데이트되도록 구현된 hook입니다.

이렇게 직접 Carousel을 구현해볼 수 있었습니다 🎉

profile
와호~!🎢 (이전 블로그: https://blog.naver.com/tulip23)

0개의 댓글