본문으로 바로가기

IntersectionObserve API, 레이지 로딩

category JavaScript 2021. 5. 13. 12:42

우아한 테크 캠프 2차 과제를 대비하면서 프로그래머스에서 과제 테스트를 연습하고 있었는데

무한 스크롤과 레이지 로딩 구현이 나오길래 연습 겸 기록을 남긴다

scroll 이벤트

단순하게 scroll 이벤트로도 레이지 로딩과 무한 스크롤이 구현 가능하다

하지만 이 방법은 두 가지 문제가 존재하는데

  1. 스크롤 이벤트를 써 본 사람이라면 알겠지만 휠을 한 번 내릴 때 마다 콘솔창에 무수히 많이 찍히는 이벤트 로그들..
    그 로그들을 봤다면 이게 성능에 영향을 준다는 것을 직감적으로 알 수 있을 것이다
  2. getBoundingClientRect()을 사용해서 특정 엘리먼트의 위치를 파악하려 할 때 DOM전체를 다시 그리는 리플로우가 발생해서 심각한 성능 저하를 가져온다

그래서 나온 것이 IntersectionObserve이다

IntersectionObserve API

IntersectionObserve은 위에서 나온 문제를 겪지 않고 매우 간단하게 특정 엘리먼트의 위치를 파악하고, 원하는 동작을 실행할 수 있게 해준다

IE가 지원을 안 하긴 하지만 나는 신경 쓸 필요가 없을 것 같다

처음에 이 API를 소개하는데 개념만 쭉 읊어놨길래 당최 뭔소린지 하나도 모르겠다가 4번 째 블로그를 정독할 때 쯤 감이 잡히기 시작했다

역시 여러 사람의 글을 다양하게 읽어보는게 답인듯

어떻게 실행되는지 부터 확인하자

See the Pen QWpEjLB by unganam (@unganam) on CodePen.

 

 

스크롤을 위아래로 내리면서 확인해보자

내가 보고있는 화면안에 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

레이지 로딩 예제

See the Pen JjWKYeW by unganam (@unganam) on CodePen.

 

    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 옵션도 줘서 예를 들고 싶었는데 코드펜에서는 적용이 되지 않아서 빼버렸다