Intersection Observer API

박상은·2022년 10월 8일
0

📀 JavaScript 📀

목록 보기
12/12

🤔 Intersection Observer API

루트 요소와 타겟 요소의 교차점을 관찰하는 API입니다.

루트 요소를 뷰포트로 하고 타겟 요소를 애니메이션을 실행할 태그로 지정한다면 스크롤에 의해서 뷰포트와 태그가 교차하는 경우에 원하는 애니메이션을 실행할 수 있습니다.

😮 Intersection Observer API 사용법

  • 옵션값들
    1. root: 루트 요소를 지정하는 속성 ( 생략 시 뷰포트 )
    2. rootMargin: 루트 요소의 범위를 확장하는 속성 ( 단위 지정 )
    3. threshold: 교차점의 범위를 지정하는 속성 0.0~1.0 ( 배열도 가능 )
  • 콜백의 매개변수
    1. boundingClientRect: 타겟 요소의 경계 사각형 정보
    2. intersectionRatio: 루트 요소와 타겟 요소의 교차 비율 ( 0.0~1.0 )
    3. intersectionRect: ?
    4. rootBounds: 루트 요소의 경계 사각형 정보
    5. target: 타겟 요소
    6. time: 교차 시간
    7. isIntersecting: 타겟 요소와 루트 요소의 threshold만큼의 교차 여부

🧐 React에서 Intersection Observer API 예시

  • 예시
const IO = new IntersectionObserver(callback, options);

IO.observe(element);
  • 사용 예시
const onScroll = useCallback((entries: IntersectionObserverEntry[], observer: IntersectionObserver) => {
  // 특정 태그가 뷰포트의 "threshold"값만큼 보여지면 현재 콜백함수 실행
}, [])

useEffect(() => {
  if(!elementRef.current) return;

  // 콜백함수와 옵션값 지정
  let observer = new IntersectionObserver(onScroll, { threshold: 0.1 });
  // 특정 요소 감시 시작
  observer.observe(elementRef.current);

  // 감시 종료
  return () => observer?.disconnect();
}, [elementRef]);
import { useRef, useEffect, useCallback } from "react";

type Direction = "up" | "down" | "left" | "right";

type Props = {
  direction?: Direction;
  duration?: number;
  delay?: number;
};
type ReturnType = {
  ref: React.MutableRefObject<any>;
  style: {
    opacity: number;
    transform: string | undefined;
  };
};

const useScrollFadeIn = ({
  direction = "up",
  duration = 1,
  delay = 0,
}: Props): ReturnType => {
  // 2022/06/14 - 애니메이션을 실행할 태그Ref - by 1-blue
  const elementRef = useRef<HTMLElement | null>(null);

  // 2022/06/14 - 지정한 방향에 따른 트랜지션 반환 - by 1-blue
  const handleDirection = useCallback((dir: Direction) => {
    switch (dir) {
      case "up":
        return "translate3d(0, 50%, 0)";
      case "down":
        return "translate3d(0, -50%, 0)";
      case "left":
        return "translate3d(50%, 0, 0)";
      case "right":
        return "translate3d(-50%, 0, 0)";
      default:
        return "";
    }
  }, []);

  // 2022/06/14 - IntersectionObserver에 등록할 콜백함수 - by 1-blue
  const onScroll = useCallback(
    ([{ isIntersecting }]: IntersectionObserverEntry[]) => {
      const { current } = elementRef;
      if (!current) return;

      // 지정한 엘리먼트가 "threshold"만큼을 제외하고 뷰포트에 들어왔다면 실행
      if (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)";
      }
      // 지정한 엘리먼트가 "threshold"만큼을 제외하고 뷰포트 밖에 존재한다면 실행
      // 아래 부분을 넣어주면 교차범위 이하일 경우 애니메이션이 반대로 실행됨
      else {
        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 = "0";
        current.style.transform = handleDirection(direction);
      }
    },
    [delay, duration, handleDirection, direction]
  );

  // 2022/06/14 - IntersectionObserver 등록 - by 1-blue
  useEffect(() => {
    if (!elementRef.current) return;

    // 좌우로 움직이는 방식을 선택했을 경우 주의해야 할 점이 전체 크기의 50%를 이동해서 뷰포트 밖으로 많이 나가버리면
    // 애니메이션 실행이 안 될 수 있다.
    // 왜냐하면 이미 뷰포트 밖으로 지정한 교차범위 이상을 나가버렸기 때문에 스크롤과 관계없이 실행되지 않음
    // 그럴 경우 "threshold" 값을 줄이거나, "rootMargin"을 늘리면 된다.
    let observer = new IntersectionObserver(onScroll, { threshold: 0.1, rootMargin: "20px" });
    observer.observe(elementRef.current);

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

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

export default useScrollFadeIn;

// <article {...useScrollFadeIn({ direction: "right", duration: 1.6 })}> 같은 형식으로 사용

0개의 댓글