[React] custom hooks 를 이용하여 scroll event 만들기

이다은·2022년 11월 8일
1

React

목록 보기
4/5

구현 사항 🚀

오늘 제가 구현하고 싶은 기능은 아래 사진처럼 항상 떠 있는 말풍선을 스크롤 했을 때 FadeIn 되도록 하는 것입니다. 처음에 생각한 방법은 현재 스크롤 된 간격을 구해서, 특정 위치에 도달하면 display: none으로 설정된 말풍선이 보여지도록 바꾸는 것이었는데, 찾아보니 hooks 를 사용하여 간편하게 구현할 수 있다고 하여 hooks를 이용한 방식으로 구현하였습니다.

구현 방법 🤔

1. custom hooks 사용 이유

제가 custom hooks를 사용한 가장 큰 이유는 반복되는 로직을 다양한 곳에서 재사용하기 위함입니다.

현재 제가 개발 중인 웹에서는 FadeIn 효과가 한 곳에서만 사용되지만, 보통 규모가 큰 웹에서는 같은 animation을 다양한 곳에서 적용하게 됩니다. 그래서 custom hooks를 사용하여 animation을 구현해 둔다면 다양한 곳에서 같은 animation을 쉽게 적용할 수 있습니다.
만약 제가 개발중인 웹에서 페이지가 추가되고 같은 애니메이션을 활용할 필요가 생긴다면 같은 커스텀 훅을 가져와서 쉽게 구현할 수 있습니다.

2. FadeIn animation 구현

아래 코드에서는 다음과 같은 기능을 합니다. 이벤트가 발생하기 전에는 ref가 가리키고 있는 element를 opacity= 0, translate3d(0, 50%, 0) 으로 숨겨 놓다가 (up 방향 기준), 이벤트가 발생하면 onScroll 함수에서 opacity=1, translate3d(0, 0, 0) 로 바꿔주어 element를 제자리에 보여줍니다.

const useScrollFadeIn = () => {
  const element = useRef();
 
  const onScroll = useCallback(
    ([entry]:any) => {
      const { current }:any = element;
      if (entry.isIntersecting) {
        current.style.transitionProperty = 'all';
        current.style.transitionDuration = `${duration}s`;
        current.style.transitionTimingFunction = 'cubic-bezier(0, 0, 0.2, 1)';
        current.style.transitionDelay = `${delay}s`;
        current.style.opacity = 1;
        current.style.transform = 'translate3d(0, 0, 0)';
      }
    },
    [delay, duration],
  );

아래 코드에서는 return 시 초기에 element를 숨겨두기 위한 style 요소를 설정합니다. 이때 박스의 css 속성에 opacity 등을 사용했다면 레이아웃이 깨질 수 있습니다. 저는 기존 박스에 opacity 속성을 따로 사용했기 때문에, 원하는 레이아웃을 만들어주기 위해 기존 박스를 감싸는 태그를 추가적으로 만들어서 FadeIn 이벤트를 적용하였습니다.
  useEffect(() => {
    let observer: any;

    if (element.current) {
      observer = new IntersectionObserver(onScroll, { threshold: 0.7 });
      observer.observe(element.current);
    }

    return () => observer && observer.disconnect();
  }, [onScroll]);

  return {
    ref: element,
    style: { opacity: 0, transform: handleDirection(direction) },
  };
};

handleDirection 부분에는 적용하고 싶은 translate3d 값 을 넣어주면 됩니다. 저는 props로 받은 방향에 따라 다른 translate3d 값을 넣어주기위해 handleDirection 이라는 함수를 만들어두고 값을 넣어주었습니다.


3. IntersectionObserver 란 ?

위에서 FadeIn 기능을 구현할 때 IntersectionObserver 라는 것이 사용되었습니다.
Intersection Observer는 타겟 엘리먼트와, 타겟 엘리먼트의 부모나 뷰포트가 교차하는 부분의 변화를 비동기적으로 관찰하는 API입니다.
Intersection Observer는 비동기적입니다. intersection이 일어날 때 인자로 넘겨준 callback을 실행시켜줍니다.

observer = new IntersectionObserver(onScroll, { threshold: 0.7 });
      observer.observe(element.current);

IntersectionObserver에는 파라미터로 특정 스크롤 시점에 실행 할 함수와 Observer 세팅 값들을 넘겨 줍니다.

  • threshold
    • threshold는 타겟이 얼만큼 보여야 callback(여기서는 onScroll 함수)이 작동할지 결정하는 값입니다.
    • 예를 들어 0.7을 넘겨주면 TargetElement가 70% 보이는 시점에 callback을 실행합니다.
    • 배열로도 넘겨줄 수 있습니다.
    • default 값은 0이며, 1px만 보여도 실행됩니다.

4. 주요 로직

  1. opacity와 translate3d를 이용하여 초기에는 element가 보이지 않게 해둡니다.
  2. Intersection Observer 를 통해 Target Element가 70% 보이는 시점에서 onScroll 함수가 실행됩니다.
  3. onScroll 함수에서 opacity와 translate3d 값을 변경하여 element를 원하는 곳에 위치시킵니다.

5. 구현 완료

FadeIn 기능 완성 !

마치며 🐱

나는 아직도 hooks가 넘 어렵다..
그래도 윈도우에 리스너를 달아서 스크롤된 위치를 구하고.. 특정 위치가 될 때마다 각 element의 css를 조절해주려고 했던 나의 초기 생각보다는 IntersectionObserver 와 hook을 사용한 위의 방법이
편리함이나 코드 재사용성면에서 더 좋은 것 같다.

또한 display 속성이 아닌 opacity를 이용해서 element를 숨기는 방식과, 윈도우에서 스크롤 값을 구하는 것이 아니라 threshold 를 이용하여 이벤트를 발생시킨다는 방식도 새로웠던 것 같다.



참고한 글

https://github.com/jus0k/scroll-hooks

https://godsenal.com/posts/React-Intersection-Observer%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EC%9D%B8%ED%94%BC%EB%8B%88%ED%8A%B8-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0/

0개의 댓글