React로 프로그래밍을 하다 보니 화면 밖으로 벗어날 때 처리가 필요했다.
내 경우에는 scroll로 화면에 벗어날 때 / 화면으로 들어올 때를 감지하는 것이 필요했다.
관련된 API를 찾아보다가, IntersectionObserver가 Web 기본 스펙에 포함되어 있어서 소개해 보고자 한다.
https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API
사용 예시
- 페이지가 스크롤 되는 도중에 발생하는 이미지나 다른 컨텐츠의 지연 로딩.
- 스크롤 시에, 더 많은 컨텐츠가 로드 및 렌더링되어 사용자가 페이지를 이동하지 않아도 되게 하는 infinite-scroll 을 구현.
- 광고 수익을 계산하기 위한 용도로 광고의 가시성 보고.
- 사용자에게 결과가 표시되는 여부에 따라 작업이나 애니메이션을 수행할 지 여부를 결정.
과거에는 Intersection 감지를 구현하기 위해 Element.getBoundingClientRect() 등을 사용했는데, 문제는 이를 계산하는 비용이 크다는 데 있다. 웹페이지에는 수많은 element가 있고, element 각각이 intersection 감지를 원한다면 메인 스레드 위에서 동작하는 다음 코드가 문제가 될 수 있다.
IntersectionObserver는 event 방식으로 동작하므로, 성능 면에서 이점이 될 수 있다. 다만, ‘정확히’ 몇 픽셀이 겹쳐졌고, 어떤 픽셀이 겹쳐졌는지는 알 수 없다.
let options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
// callback으로 들어오는 element는 배열!
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
});
};
let observer = new IntersectionObserver(callback, options);
let target = document.querySelector('#listItem');
observer.observe(target); // observer.unobserve(target) 으로 해제
IntersectionObserver로 줄 수 있는 옵션은 3가지가 있다.
- root: 기본값은 브라우저 뷰포트로, 대상 객체의 가시성을 확인할 때 사용하는 요소이다.
- rootMargin: root 요소의 bouding box를 수축, 증가 가능하다. CSS의 margin처럼 사용할 수 있다. (ex: “10px 20px 30px 40px ”)
- threshold: 콜백이 실행될 가시성 퍼센테이지를 의미하는 숫자, 숫자 배열이다. 25% 단위로 가시성이 변경되었을 때 실행되게 하려면 [0, 0.25, 0.5, 0.75, 1] 와 같은 배열로 설정하면 된다.
여기서 Intersection은 항상 직사각형으로 계산되는 점에 유의해야 한다. 직사각형이 아닌 모양의 경우 가장 작은 직사각형으로 변형되어 계산된다.
내 경우에는 dropdown을 custom으로 만드는데 사용했다.
- Dropdown의 selectable한 옵션을 기존에는 상위 컴포넌트인 dropdown에 relative + 하위 컴포넌트인 옵션에 absolute를 주어 해결했다. 다만 이 경우에는 모달에 표시하는 경우 아래로 스크롤이 생겨버렸다.
- 의도된 동작은 항상 화면 위로 표시하는 것이었기에 position을 fixed로 주어야 했다. 하지만 이 경우에도 스크롤을 움직였을 경우 옵션이 dropdown을 따라다니지 못하는 문제가 있었다.
- 그래서 position을 fixed로 주되, scroll, resize 이벤트가 발동시 getBoundingClientRect를 계산해서 따라다니도록 설정했다. 그러나 dropdown이 화면에서 벗어난 경우에도 option은 화면에 남아있는 경우가 있었다.
- position fixed + scroll, resize 발생시 getBoundingClientRect 계산 + IntersectionObserver로 화면 벗어난 경우 option을 absolute로 변경해서 해결!
useEffect(() => {
const anchorEl = refAnchor.current ?? null; // dropdown anchor
if (!anchorEl) {
return;
}
// anchor를 움직일 수 있는 모든 이벤트에 대해서 rect를 다시 계산
["scroll", "resize"].forEach(event => {
window.addEventListener(event, updateRect, true);
});
// anchor가 scroll 등의 이유로 화면 밖으로 사라지면 anchor hidden 값을 true로 세팅
// optionListPlaceToTop가 true라도 anchor가 화면상에 존재하지 않으면 fixed가 아닌 absolute 처럼 동작
const observer = new IntersectionObserver((entries, _) => {
entries.forEach(entry => {
setIsAnchorHidden(!entry.isIntersecting); // isAnchorHidden으로 fixed / absolute 값 결정
});
});
observer.observe(anchorEl);
// destructor
return () => {
observer.unobserve(anchorEl);
["scroll", "resize"].forEach(event => {
window.removeEventListener(event, updateRect, true);
});
};
}, []);
간단한 Dropdown 같아 보이지만, 생각보다 알아야 할 지식이 많다 😅
'frontend' 카테고리의 다른 글
가로 세로 스크롤 되는 테이블 만들기 (0) | 2023.04.24 |
---|---|
[TIL] html에서 돋보기 뷰 구현하기 (0) | 2023.04.03 |
[팁] javascript console 객체 알아보기 (0) | 2022.03.01 |
[팁] Array.splice로 배열 전체 replace하기! (0) | 2022.02.11 |
Tailwindcss 적용해보기! (0) | 2022.02.05 |