01_자바스크립트 스크롤 이벤트의 한계
자바스크립트에서 무한스크롤 구현방법과 문제점
addEventListner()
에 scroll 이벤트 이용하여 구현 가능
getBoundingClientRect()
메서드로 원하는 특정위치에서 다음 페이지들을 가져오도록 구현
이 두개의 방법은 다음과 같은 이유로 성능문제를 발생시킨다.
scroll 이벤트 : 단시간에 수백번 호출이 되며 동기적으로 실행
const list = document.querySelector('#infinte-list'); let nextItem = 1 const loadMore = () => { for(let i=0; i<20; i++){ let item = document.createElement('li') item.innerText = 'List Item #' + nextItem++ list.appendChild(item) } } //ul 리스트 바닥까지 스크롤했는지 확인 //스크롤은 단기간에 수백번 호출하여 성능 최악. 각 엘리먼트마다 이벤트가 등록되어있을 경우 몇배로 성능문제가 발생 list.addEventLister('scroll',function(){ if(list.scrollTop + list.clientHeight >= list.scrollHeight){ loadMore() } }) //아이템 20개씩 더 가져오는 loadMore 함수 실행 loadMore()
getBoundingClientRect() : 엘리먼트크기와 뷰포트의 상대적인 위치 정보를 제공하는 dom 객체를 반환하여 우리가 원하는 특정 위치를 정할 수 있다. 하지만 계산을 할때마다 리플로우 현상이 일어난다.
리플로우 현상이란 ?
→ 문서 내 요소에 위치한 도형을 다시 계산하기 위한 웹브라우저의 프로세스 이름으로, 문서의 일부나 전체를 다시 렌더링 하는데 사용하는 개념
이 두개의 방법은 메인 스레드에서 실행이 되기 때문에 이 중 하나라도 호출되면 성능문제를 일으킬 수 있다.
그 대안으로 Intersection Observer 를 사용하여 비동기적으로 교차점을 관찰하여 성능을 개선 할 수 있다.
02_Intersection Observer 란?
브라우저 뷰포트와 원하는 요소의 교차점을 관찰하며, 요소가 뷰포트에 포함되는지 아닌지 구별하는 기능
- 비동기적으로 실행되어 메인스레드에 영향을 주지 않으면서 요소들의 변경사항을 관찰한다.
- scroll의 이벤트 연속호출의 문제와 getBoundingClientRect 의 성능문제 해결
- intersectionObserverEntry 등 속성을 활용하여 요소들의 위치를 알 수 있다.
- 여러상황에서 사용 가능
- 페이지 스크롤 되는 도중 발생하는 이미지 지연 로딩
- 자동으로 페이지 하단에 스크롤 했을 때 무한스크롤 구현
- 광고 수익계산을 위한 광고 가시성 보고하기 위해 사용
const io = new IntersectionObserver((entries)=>{ entries.forEach(()=>{ //관찰대상이 뷰포트 안에 들어온 경우 active 클래스 추가 if(entry.intersectionRatio > 0){ entry.target.classList.add('active') }else{ entry.target.classList.remove('active') } }) }) //관찰할 대상을 선언하고 해당속성을 관찰 const boxList = document.querySelectorAll('.box') boxList.forEach((el)=>{ io.observer(el) })
03_Intersection Observer 기본문법
어떤 상황에서 콜백함수를 호출할까
- 대상(target) 요소가 기기 뷰포트나 특정 요소와 교차할 때
- 관찰자(observer) 가 최초로 타겟을 관측하도록 요청받을때
Intersection Observer() 는 new IntersectionObserver 라는 생성자를 통해 인스턴스를 만들고 시작한다.
해당 인스턴스로 관찰자를 초기하고 관찰할 대상을 등록하면 된다.
이때 콜백함수와 옵션, 이 두개의 인자를 갖는다.
- callback : 관찰할 대상이 등록되거나 가시성에 변화가 생기면 실행. (entries=[], observer 두개의 인자를 가진다.)
- Options : 관찰이 시작되는 상황에 대한 옵션을 설정할 수 있다. (root, rootMargin, threshold)
//observer 초기화 let io = new IntersectionObserver(callback, options) //관찰할 대상 등록 io.observe(element)
✳️ Intersection Observer Callback : Entry 속성
읽기전용의 여러가지 속성들을 포함
- boundingClientRect : 관찰 대상의 경계 사각형을 DOMRectReadOnly 로 반환
- getBoundingClientRect() 와 동일한 값을 얻으나 reflow 없이 계산한다는 점에서 차이가 있다.
- intersectionRect : 관찰 대상의 교차한 영역 정보를 DOMRectReadOnly 로 반환
- 뷰포트 안에 들어온 요소 정보만
- intersectionRatio : 관찰 대상의 교차한 영역 비율을 0.0 과 1.0 사이의 숫자로 반환
- 만약 상위 50% 라면 0.5, 상위 10%만 반환하면 0.1 이런식으로 반환된다.
- isIntersecting : 관찰 대상이 교차 상태인지 아닌지 불리언값으로 반환
- 뷰포트 내 하나라도 걸린다면 true 값을 반환하고 아니면 false 반환
- rootBounds : 지정한 루트 요소의 사각형 정보를 DOMRectReadOnly 로 반환
- 기본적으로 전체 뷰포트가 루트요소가 되고, rootMargin 값으로 루트 요소의 크기를 변경할 수 있다.
- target : 관찰대상 요소(Element) 반환
- time : 변경이 발생한 시간 정보 (DOMHighResTimeStamp) 반환
let callback = (entreis, observer) => { entries.forEach(entry => { //Each entry describes an intersection change for on observed //target element : //entry.boundingClientRect ~ entry.time }) }
✳️ Intersection Observer Options
Options 를 통해 관찰이 시작되는 상황에 대한 옵션을 설정할 수 있다.
기본값이 있어 필수는 아니다.
- root : 대상 객체(target) 의 가시성을 확인할 때 사용되는 뷰포트 요소. 반드시 대상 객체의 “조상” 요소여야 한다. 혹시 null || undefined 라면 브라우저의 뷰포트가 기본값으로 설정된다.
- rootMargin : root가 가진 바깥 여백(Margin). margin 값(px, %)을 이용해 root 범위를 확장/축소 할 수 있다.
- ex - 10px 20px 30px 40px (top,right,bottom,left) , default = 0
- threshold : observer의 콜백이 실행될 대상요소(target) 의 가시성이 얼마나 필요한지 나타내는 값. 기본값은 0 이다.
- 0인경우에는 타겟의 가장자리 픽셀이 루트범위를 바로 교차하는 순간 observer 가 실행된다.
- 0.1 인 경우 타겟의 가시성이 10%일 경우 실행이 된다.
- 배열안에 0, 0.2, 1 일 경우 타겟의 가시성이 0,20%,100%일때 observer 가 모두 실행이 된다.
let options = { root : document.querySelector('#scrollArea'), rootMargin:'0px', threshold: 1.0, //타겟이 100% 뷰포트 안에 들어왔을 때 옵저버가 실행이 된다. } let observer = new IntersectionObserver(callback, options)
✳️ Intersection Observer Method
- observe : 대상 요소(target)의 관찰을 시작할 때 사용
- unobserve : 대상 요소의 관찰을 중지할 때 사용. 관찰을 중지할 하나의 대상 요소를 인수로 지정해야 한다.
- disconnect : intersectionObserver 인스턴스가 관찰하는 모든 요소의 관찰을 중지할 때 사용
let io = new IntersectionObserver(callback, options) const div = document.querySelector('div') const li = document.querySelector('li') io.observe(div) //div 요소 관찰 io.unobserve(li) //li 요소 관찰 중지 io.disconnect() //io 가 관찰하는 모든 요소(div,li) 관찰 중지