IntersectionObserve API, 레이지 로딩
우아한 테크 캠프 2차 과제를 대비하면서 프로그래머스에서 과제 테스트를 연습하고 있었는데
무한 스크롤과 레이지 로딩 구현이 나오길래 연습 겸 기록을 남긴다
scroll 이벤트
단순하게 scroll 이벤트로도 레이지 로딩과 무한 스크롤이 구현 가능하다
하지만 이 방법은 두 가지 문제가 존재하는데
- 스크롤 이벤트를 써 본 사람이라면 알겠지만 휠을 한 번 내릴 때 마다 콘솔창에 무수히 많이 찍히는 이벤트 로그들..
그 로그들을 봤다면 이게 성능에 영향을 준다는 것을 직감적으로 알 수 있을 것이다 - getBoundingClientRect()을 사용해서 특정 엘리먼트의 위치를 파악하려 할 때 DOM전체를 다시 그리는 리플로우가 발생해서 심각한 성능 저하를 가져온다
그래서 나온 것이 IntersectionObserve이다
IntersectionObserve API
IntersectionObserve은 위에서 나온 문제를 겪지 않고 매우 간단하게 특정 엘리먼트의 위치를 파악하고, 원하는 동작을 실행할 수 있게 해준다

IE가 지원을 안 하긴 하지만 나는 신경 쓸 필요가 없을 것 같다
처음에 이 API를 소개하는데 개념만 쭉 읊어놨길래 당최 뭔소린지 하나도 모르겠다가 4번 째 블로그를 정독할 때 쯤 감이 잡히기 시작했다
역시 여러 사람의 글을 다양하게 읽어보는게 답인듯
어떻게 실행되는지 부터 확인하자
스크롤을 위아래로 내리면서 확인해보자
내가 보고있는 화면안에 item이 들어오면 item박스가 커지고 화면 밖으로 나가면 작아지는게 전부인 간단한 일을 하고있다
const option = {};
const io = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('change');
return
}
entry.target.classList.remove('change');
});
}, option);
[...document.querySelectorAll('.item')].forEach(item=>io.observe(item));
자바스크립트 코드는 위와 같다
우선 제일 첫 줄에 있는 option은 무시해도 좋다 지금은 사용하지 않고있으니까 실제로 값을 주지 않아도 잘 작동한다
1. [...document.querySelectorAll('.item')].forEach(item=>io.observe(item));
이해를 돕기 위해 마지막 줄 부터 먼저 확인해볼텐데
document.querySelectorAll 로 가져온 엘리먼트들은 배열로 작동하는 것 처럼 보이지만 실제로는 유사 배열이다
이대로 사용해도 상관없지만 spread문법을 이용해서 가져온 아이템들을 배열로 만들어주었다
배열로 만들어 준 후에 forEach을 이용해 io.observe(item)으로 가져온 아이템들을 지켜보고 있다 라고 생각하면 편하다
무엇을 하고 있는지는 이따 알아보자
2. new IntersectionObserve()
IntersectionObserve객체를 하나 만드는 것이다
이 객체를 만드는데는 콜백 함수 하나와 옵션이 하나씩 필요하다
옵션은 위에서 처럼 값을 주지 않아도 잘 돌아가기 때문에 나중에 알아보고
(entries, observer) => {
}
콜백 함수는 이러한 형태를 띠고 있다
여기서 파라미터로 받는 entries는 1번에서 쳐다보고 있는 아이템들이 entries로 온다
observer는 자기 자신
entries.forEach()
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('change');
return
}
entry.target.classList.remove('change');
});
entries.forEach로 반복문을 돌린다
isIntersecting은 내가 쳐다보고 있는 아이템이 화면 안에 들어왔는지 여부를 boolean값으로 알려준다
코드를 풀어보면 아이템이 내가 보고 있는 화면 안에 들어왔는지 확인하고 그 값이 true라면 아이템에 change클래스를 주고
그 값이 false라면 change클래스를 삭제한다
option
옵션은 총 3가지 설정값을 갖는다
- root : 화면 지정, 어느 화면에 들어왔을 때 동작을 실행시킬 것인지 화면을 지정하는 것이다 기본값은 내가 보는 화면, view-port
- rootMargin : 말 그대로 여백값을 주는건데 루트에서 지정한 화면 넓이보다 크기를 키울 수 있다 기본값은 0px 0px 0px 0px
- threshold : 어느 정도 화면에 들어왔을 때 동작을 실행시킬 것인지 정하는 값 클수록 화면에 많이 들어와야하며 기본값은 0
레이지 로딩 예제
const option = {};
const io = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target);
}
});
}, option);
[...document.querySelectorAll('.item')].forEach(item=>io.observe(item));
달라진 점은 entry에 이미지 src를 전달해주고 나서 unobserve로 지켜보는 것을 그만두었다는 것
이미 이미지를 불러왔기 때문에 더 이상 그 엘리먼트를 관찰할 필요가 없기 때문
rootMargin 옵션도 줘서 예를 들고 싶었는데 코드펜에서는 적용이 되지 않아서 빼버렸다