루트 요소와 타겟 요소의 교차점을 관찰하는 API
입니다.
루트 요소를 뷰포트로 하고 타겟 요소를 애니메이션을 실행할 태그로 지정한다면 스크롤에 의해서 뷰포트와 태그가 교차하는 경우에 원하는 애니메이션을 실행할 수 있습니다.
root
: 루트 요소를 지정하는 속성 ( 생략 시 뷰포트 )rootMargin
: 루트 요소의 범위를 확장하는 속성 ( 단위 지정 )threshold
: 교차점의 범위를 지정하는 속성 0.0
~1.0
( 배열도 가능 )boundingClientRect
: 타겟 요소의 경계 사각형 정보intersectionRatio
: 루트 요소와 타겟 요소의 교차 비율 ( 0.0
~1.0
)intersectionRect
: ?rootBounds
: 루트 요소의 경계 사각형 정보target
: 타겟 요소time
: 교차 시간isIntersecting
: 타겟 요소와 루트 요소의 threshold
만큼의 교차 여부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 })}> 같은 형식으로 사용