Intersection Observer
가 그 기능을 충실히 해줄거라고 생각했고, 시도했다.이 gif 처럼, 테스트박스가 스르륵 나오는것!
먼저 간단하게 정리하고 가자.
- Intersection Observer API는 다음과 같은 상황에 호출되는 콜백을 생성하는 기능을 제공합니다:
(1) 대상(target) 으로 칭하는 요소가 기기 뷰포트나 특정 요소(이 API에서 이를 root 요소 혹은 root로 칭함)와 교차함.
(2) observer가 최초로 타겟을 관측하도록 요청받을 때마다.intersection observer
를 생성하기 위해서는 생성자 호출 시 콜백 함수를 제공해야 합니다. 이 콜백 함수는 threshold가 한 방향 혹은 다른 방향으로 교차할 때 실행됩니다.
let observer = new IntersectionObserver(callback, options);
IntersectionObserver()
생성자에 전달되는 options 객체는 observer 콜백이 호출되는 상황을 조작할 수 있습니다.
let options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
아래부터는 옵션에 관한 설명이다.
설명이 길어 조금 복잡하게 느껴질 수 있으나, 각 옵션이 어떠한 역할을 하는지 알 수 있으니 관심있다면 찬찬히 보는것을 추천한다.
(해당 내용은 MDN 내용이니 믿고 봐도 된다..!)
root
대상 객체의 가시성을 확인할 때 사용되는 뷰포트 요소입니다. 이는 대상 객체의 조상 요소여야 합니다. 기본값은 브라우저 뷰포트이며, root
값이 null
이거나 지정되지 않을 때 기본값으로 설정됩니다.
컨테이너와 요소의 교차점을 추적하기 전에 그 컨테이너가 무엇인지 알아야 합니다. 그 컨테이너는 교차점 루트 또는 루트 요소입니다. 이 요소는 관찰할 요소의 조상인 문서에 있는 특정 요소이거나 컨테이너로 문서의 뷰포트를 사용하기 위해 null일 수 있습니다.
루트 교차 사각형은 대상 또는 대상과 비교하는 데 사용되는 사각형입니다. 이 사각형은 다음과 같이 결정됩니다.
rootMargin
root 가 가진 여백입니다. 이 속성의 값은 CSS의 [margin](https://developer.mozilla.org/ko/docs/Web/CSS/margin)
속성과 유사합니다. e.g. "10px 20px 30px 40px"
(top, right, bottom, left). 이 값은 퍼센티지가 될 수 있습니다. 이것은 root 요소의 각 측면의 bounding box를 수축시키거나 증가시키며, 교차성을 계산하기 전에 적용됩니다. 기본값은 0입니다.
threshold
observer의 콜백이 실행될 대상 요소의 가시성 퍼센티지를 나타내는 단일 숫자 혹은 숫자 배열입니다. Intersection Observer API는 대상 요소의 가시성이 미세하게 변경될 때마다 보고하지 않습니다. 대신, 관찰자를 생성할 때 대상 요소의 가시성 비율을 나타내는 하나 이상의 숫자 값을 제공할 수 있습니다. 그런 다음 API는 이러한 임계값을 넘는 가시성 변경만 보고합니다.
만일 50%만큼 요소가 보여졌을 때를 탐지하고 싶다면, 값을 0.5
로 설정하면 됩니다. 혹은 25% 단위로 요소의 가시성이 변경될 때마다 콜백이 실행되게 하고 싶다면 [0, 0.25, 0.5, 0.75, 1]
과 같은 배열을 설정하세요. 기본값은 0
이며(이는 요소가 1픽셀이라도 보이자 마자 콜백이 실행됨을 의미합니다). 1.0
은 요소의 모든 픽셀이 화면에 노출되기 전에는 콜백을 실행시키지 않음을 의미합니다.
threshold
) 을 충족할 때마다 콜백이 호출됩니다.oberver
)를 수신합니다.intersectionRect : 요소의 교차 사각형입니다.
isIntersecting : 요소가 교차하고 있는지 여부입니다.
intersectionRatio : 요소가 교차하는 비율입니다.
rootMargin : 교차 영역을 계산할 때 고려되는 루트 요소의 마진입니다.
boundingClientRect : 루트 요소의 사각형입니다.
intersectionObserverEntryIndex : 이 항목이 IntersectionObserver의 결과 목록에서 차지하는 순서입니다.
let callback = (entries, observer) => {
entries.forEach(entry => {
// Each entry describes an intersection change for one observed
// target element:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
});
};
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.intersectionRatio >= 0.5) {
// 요소가 뷰포트의 50% 이상을 차지합니다.
}
});
});
const target = document.getElementById("target");
observer.observe(target);
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
console.log(entry.intersectionRatio);
});
});
const target = document.getElementById("target");
observer.observe(target, {
thresholds: [0.25, 0.5, 0.75] //target 요소가 뷰포트의 25%, 50%, 75%를 차지할 때마다 콜백 함수를 호출
});
- 임의의 div를 결제페이지 최하단에 두고, useRef로 해당 div를 인식한다.
const PaymentInfor = () => {
const observeRef = useRef(null);
return (
<>
<여러 코드들 ..>
<div ref={observeRef} />
</>
);
};
- 저 div가 보이면, 모달처럼 div를 보여줄 것이다.
const [isObserverRefFind, setIsObserverRefFind] = useState(false);
useEffect(() => {
const scrollObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => setIsObserverRefFind(entry.isIntersecting)),
{
root: null,
rootMargin: "0px",
threshold: 1,
};
});
if (observeRef.current) {
scrollObserver.observe(observeRef.current);
}
return () => {
if (observeRef.current) {
// scrollObserver.unobserve(observeRef.current);
scrollObserver.disconnect();
}
};
}, []);
위 코드에서 중요하게 볼 사항은
entries.forEach((entry) => setIsObserverRefFind(entry.isIntersecting)),
이 부분이다.
감지가 되면, entry.isIntersecting
은 boolean 값으로 true 로 바뀌는데 이 값을 useState인 setIsObserverRefFind
가 받는다. 그렇기에, isObserverRefFind
처음 상태 false 에서 감지되면 true로 바뀌게 된다.
- 그래서 true가 되면 PaymentSlide를 보여주게 되는데, isObserverRefFind값을 넘겨주어 CSS로 처리하게된다.
return (
<>
<PaymentInforwrap>
<LectureInfo>
{isLoading ? (
<div>신청정보 가져오는중..</div>
) : (
<PaymentLectureInfo productDatas={productDatas} />
)}
</LectureInfo>
<LectureInfo>
<PaymentSelect
radioValue={radioValue}
setRadioValue={setRadioValue}
/>
</LectureInfo>
</PaymentInforwrap>
<PaymentSlide
isObserverRefFind={isObserverRefFind}
productDatas={productDatas}
radioValue={radioValue}
/>
<div ref={observeRef} />
</>
);
//PaymentSlide.tsx
const PaymentSlide = ({ isObserverRefFind }) => {
return (
<PaymentSlideContainer className={isObserverRefFind ? "show" : "hide"}>
<PaymentDetails>
<PaymentDetailDiv>
~~~내용~~~
</PaymentDetailDiv>
</PaymentDetails>
<UserAgreeContainer>
~~~내용~~~
</UserAgreeContainer>
</PaymentSlideContainer>
);
};
export default PaymentSlide;
const showAnimation = keyframes`
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
`;
const hideAnimation = keyframes`
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(10px);
}
`;
const PaymentSlideContainer = styled.div`
width: 100%;
padding-left: 6.1%;
padding-right: 6.1%;
background-color: #f2f2f2;
position: fixed;
bottom: 0;
left: 0;
z-index: 30;
opacity: 0;
transform: translateY(20px);
animation-fill-mode: forwards;
animation-duration: 0.3s;
&.show {
animation-name: ${showAnimation};
}
&.hide {
animation-name: ${hideAnimation};
}
`;
isObserverRefFind
의 값을 계속 PaymentInfor
에서 있으면 보이고 아니면 빈 div를 보여주게 설정했더니 사라지는게 안됐는데, 계속 고민하다 코드 수정해서 해결할 수 있었다.entries.forEach((entry) => setIsObserverRefFind(entry.isIntersecting)),
~~
if (observeRef.current) {
scrollObserver.observe(observeRef.current);
}
출처
https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API