구현하고자 하는 기능은 ✨전 게시글✨ 참조
특정 요소의 가시성(화면에 나타나는지 사라졌는지!)에 따라 어떤 동작을 수행해야 할 때. 무한스크롤이나 지연 로딩, 그리고 어떤 요소가 뷰포트에 들어왔을 때 애니메이션 발생시키기... 등! 스크롤 동작과 관련된 여러 작업에 사용할 수 있다. document의 top부터 bottom까지 모든 스크롤 움직임을 감지하는 게 아니라 특정한 요소의 가시성을 관찰하는 것이기 때문에 그냥 scroll 이벤트 + throttle을 사용하는 것보다 더 효율적이라고 함!
1. IntersectionObserver 생성하기
const Observer = new IntersectionObserver(callback, [option])
2. 콜백함수 설정하기
콜백함수는 인자로 entries를 받는다. entries는 entry를 담은 배열인데, entry는 해당 Observer가 관찰하고 있는 요소들을 말한다. (이따 4번에서 Observer가 관찰할 특정 요소 혹은 요소들을 지정해 줘야 함!)
🔺 콘솔로그로 entries를 찍었을 때
(entry들을 담은 배열임을 알 수 있다!)
🔺 entries의 요소인 entry를 찍었을 때
보이는 것처럼 entry는 저렇게나 많고 뭔지 모를.. 프로퍼티를 가진다...😬 그중 중요한 것만 정리해 보자면!
- isIntersecting
후에 설정할 옵션을 고려해서, 타겟이 현재 root에서 관찰되는지 여부! boolean으로 표시- boundingClientRect
target의 xy좌표 같은 위치 정보나 width, heigth 같은 치수 정보를 담은 객체- target
관찰 중인 타겟 엘리먼트그래서 isIntersecting의 true/false 값을 이용해 타겟이 관찰될 때 실행할 코드를 콜백함수에 넣어줄 수 있다!
const callback = (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { 타겟이 관찰될 때 실행할 코드 } } }
3. 옵션 설정하기
옵션 객체는 root, rootMargin, threshold를 가질 수 있다.
- root
타겟이 될 요소의 가시성을 확인할 요소! 당연히 타겟의 조상요소 중 하나여야 하고, 디폴트 값은 브라우저 뷰포트.- rootMargin
css margin처럼px
이나%
로, 그리고top right bottom left
순으로 설정할 수 있다. (차이점은 0이더라도 꼭 단위를 붙여줘야 함!) 양수 값을 주면 root 영역이 확대되고, 음수 값을 주면 root 영역이 감소한다. 예를 들어 {rootMargin: '0 0 -100px 0'}으로 설정할 경우 root의 bottom 영역이 줄어들어서, 뷰포트의 끝에서 100px 더 올라와야 관찰된 걸로 인지됨!- threshold
대상이 얼만큼 관찰되었을 때 콜백함수를 실행할지 설정하는 옵션. 0에서 1 사이의 값을 가질 수 있고, 0일 경우 1px이라도 관찰되었을 때, 1이면 요소 전체가 root 영역에 들어왔을 때를 의미한다. 디폴트는 0.const option = {rootMargin: '-10%', threshold: 0.5}
이런 식으로 필요한 옵션만 설정할 수도 있고, 옵션이니까 아예 생략도 가능가능~~~
4. 관찰할 타겟 요소 지정해 주기
콜백함수와 옵션까지 넣어 열심히 만든 옵저버에게... 이제 특정 요소를 관찰하라고 명령을 해 줘야 한다! observe 메서드에 인자로 타겟이 될 엘리먼트를 넣어주면 되는데, React에선 DOM node를 직접 선택할 수 없기 때문에 이전 글에서 공부한 useRef를 이용해야 한다.
const target = useRef(); ... observer.observe(target.current); ... <div ref={target}>...</div>
여기까지 잘 했다면! 타겟이 관찰될 때마다 콜백함수를 실행하는 IntersectionObserver 완성~~~ (아마도...ㅎㅎ)
관찰할 요소에 fetch 받아와야 하는 데이터가 포함되어 있어서 fetch가 끝나면 실행하도록 하기 위해 useEffect에 넣어줬고, 관찰할 요소가 여러 개라서 타겟 설정 과정에서도 forEach를 사용했다. 결론적으로 쪼끔 더 복잡해진 코드...
const tabRef = useRef([]);
const [currentTab, setCurrentTab] = useState();
useEffect(() => {
const changeTab = entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
setCurrentTab(entry.target);
}
});
};
const observerOption = { rootMargin: '-10% 0px', threshold: 0.1 };
const tabObserver = new IntersectionObserver(
changeTab,
observerOption
);
tabRef.current.forEach(tab => tabObserver.observe(tab));
return () => tabObserver.disconnect();
}, [course]);
🔻 아직 데이터는 미완성이지만..... 결과물
🔻 모바일 버전도 잘 작동함~~~!
별거 아니게 생겨서 얕봤는데 은근 오래걸렸다....😖
그래도 이번 기회에 useRef랑 IntersectionObserver에 대해 나름 깊이 공부한 것 같아서 뿌듯✨✨
너무 멋져요~~!!! 도은님은 프론트의 끝판왕🕶