Intersection Observer API는 타겟 요소와 상위 요소 또는 최상위 document의 viewport 사이의 intersection내의 변화를 비동기적으로 관찰하는 방법입니다.
일반적으로 알려진 해결방법은 신뢰성이 부족하고 브라우저나 사이트에 부하를 주기때문에 좋지 못한 사용자 경험을 낳습니다.
일반적인 방법의 문제점
과거에 intersection 감지를 구현하면 영향을 받는 모든 요소를 알기 위해서 Element.getBoundingClientRect()와 같은 메서드를 호출하는 여러 이벤트 핸들러와 루프가 얽혀있었습니다. 모든 코드가 메인 스레드에서 실행되기 때문에, 이 중 하나라도 성능 문제를 일으킬 수 있습니다.
intersection 정보는 아래와 같은 여러가지 이유 때문에 필요합니다.
- 페이지가 스크롤 되는 도중에 발생하는 이미지나 다른 컨텐츠의 지연 로딩.
- 스크롤 시에, 더 많은 컨텐츠가 로드 및 렌더링되어 사용자가 페이지를 이동하지 않아도 되게 하는 infinite-scroll 을 구현.
- 광고 수익을 계산하기 위한 용도로 광고의 가시성 보고.
- 사용자에게 결과가 표시되는 여부에 따라 작업이나 애니메이션을 수행할 지 여부를 결정
Intersection Observer API 는 그들이 감시하고자 하는 요소가 다른 요소(viewport)에 들어가거나 나갈때 또는 요청한 부분만큼 두 요소의 교차부분이 변경될 때 마다 실행될 콜백 함수를 등록할 수 있게 합니다.
intersection observer 생성
intersection observer를 생성하기 위해서는 생성자 호출 시 콜백 함수를 제공해야 합니다.
이 콜백 함수는 threshold가 한 방향 혹은 다른 방향으로 교차할 때 실행됩니다.
threshold: 1.0 은 대상 요소가 root 에 지정된 요소 내에서 100% 보여질 때 콜백이 호출될 것을 의미합니다.
Intersection observer 설정
전달되는 options 객체는 observer 콜백이 호출되는 상황을 조작할 수 있습니다.
root
대상 객체의 가시성을 확인할 때 사용되는 뷰포트 요소입니다. 이는 대상 객체의 조상 요소여야 합니다.
기본값은 브라우저 뷰포트이며, root 값이 null 이거나 지정되지 않을 때 기본값으로 설정됩니다.
rootMargin
root 가 가진 여백입니다.
이 속성의 값은 CSS의 margin 속성과 유사합니다. 기본값은 0입니다.
threshold
observer의 콜백이 실행될 대상 요소의 가시성 퍼센티지를 나타내는 단일 숫자 혹은 숫자 배열입니다.
만일 50%만큼 요소가 보여졌을 때를 탐지하고 싶다면, 값을 0.5로 설정하면 됩니다.
observe()
IntersectionObserver의 주시 대상 목록에 요소를 추가합니다.
하나의 감지기는 하나의 루트와 하나의 역치 목록만 가질 수 있지만, 동시에 여러 요소를 주시할 수 있습니다.
지정한 주시 대상 요소의 가시성 비율이 감지기의 역치(IntersectionObserver.thresholds (en-US))를 통과하는 순간 감지기 콜백이 호출됩니다.
IntersectionObserver.observe(targetElement);
루트 내에서의 가시성 변화를 감지할 element입니다.
루트 요소의 자손이어야 합니다.
루트가 현재 문서의 뷰포트일 경우 이 요소도 문서 내에 위치해야 합니다.
intersection observer를 이용한 무한 스크롤 구현
import { useEffect, useRef, useState } from 'react'
import Item from './Item'
import Loader from './Load'
import styles from './infinite.module.css'
const InfiniteScroll = () => {
const target = useRef<HTMLDivElement>(null)
const [isLoaded, setIsLoaded] = useState(false)
const [itemLists, setItemLists] = useState([1])
const getMoreItem = async () => {
setIsLoaded(true)
await new Promise(resolve => setTimeout(resolve, 1500))
let Items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
setItemLists(itemLists => itemLists.concat(Items))
setIsLoaded(false)
}
const onIntersect: IntersectionObserverCallback = async (
[entry]: IntersectionObserverEntry[],
observer: {
unobserve: (arg0: Element) => void
observe: (arg0: Element) => void
}
) => {
if (entry.isIntersecting && !isLoaded) {
observer.unobserve(entry.target)
await getMoreItem()
observer.observe(entry.target)
}
}
useEffect(() => {
let observer: IntersectionObserver
if (target) {
observer = new IntersectionObserver(onIntersect, {
threshold: 1,
})
observer.observe(target.current!)
}
return () => observer && observer.disconnect()
}, [target])
return (
<>
{/* <GlobalStyle /> */}
<div className={styles.AppWrap}>
{itemLists.map((v, i) => {
return <Item number={i + 1} key={i} />
})}
<div ref={target} className={styles.TargetElement}>
{isLoaded && <Loader />}
</div>
</div>
</>
)
}
export default InfiniteScroll
'프론트엔드' 카테고리의 다른 글
디버거 활용법 (0) | 2022.06.10 |
---|---|
상태관리 라이브러리 Redux vs Mobx vs Recoil 전격비교 (0) | 2022.06.05 |
next.js + typeScript + boothStrap + module.css 셋업방법 (0) | 2022.06.02 |
BOM과 DOM (0) | 2022.04.18 |
프로토타입 체인 (0) | 2021.10.05 |