[JS] Intersection Observer API를 활용한 lazy loading 구현

Minsu Han·2022년 8월 31일

Javascript

목록 보기
4/7
post-thumbnail

앞서 Sticky navigation 구현 포스트에서 소개했던 Intersection Observer API를 활용하여 이번에는 lazy loading이라는 UX를 구현해 보자.

웹페이지 내에 이미지와 같이 용량이 큰 요소들이 포함되는 경우 구형 기기나 속도가 느린 네트워크 환경에서 이미지가 늦게 표시되는 현상이 발생할 수 있다. 이를 방지하기 위해 사용자가 특정 위치(ex. 로드할 이미지보다 100px 위)로 스크롤하는 시점에 이미지가 표시되도록 하는 것을 lazy loading이라 한다.

구현할 예제에서는 처음 웹페이지가 로드될 때에는 아래와 같이 크기와 용량이 작은 이미지에 블러 처리하여 표시하다가, 아래로 스크롤하며 이미지가 뷰포트의 200px만큼 올라온 시점에서 원본 이미지로 대체하고 블러 처리를 제거하여 표시한다.

이미지에 해당하는 HTML을 살펴보면 다음과 같다. img 태그의 src가 저용량의 이미지 경로로 되어 있다. lazy loading될 때 src는 아래 data-src의 원본 이미지 경로로 대체될 것이다.

<section class="section" id="section--1">
      <div class="section__title">
        <h2 class="section__description">Features</h2>
        <h3 class="section__header">
          Everything you need in a modern bank and more.
        </h3>
      </div>

      <div class="features">
        <img
          src="img/digital-lazy.jpg"
          data-src="img/digital.jpg"
          alt="Computer"
          class="features__img lazy-img"
        />
        ...

그리고 아래 css는 블러 처리 효과를 주는 lazy-img 클래스이다. 원본 이미지가 로드될 때 img태그의 class에서 이 클래스를 제거한다.

.lazy-img {
  filter: blur(20px);
}

이제 lazy loading을 구현해 보자.

  1. data-src 속성을 갖는 이미지 요소들(imgTargets)을 선택한 다음, 각각을 observe하도록 만든다.
  2. imgObserver의 콜백 함수 loadImg는 이미지 요소가 뷰포트와 rootMargin 만큼 교차한 시점에 타겟 요소의 src 값을 data-src 값으로 대체한다.
  3. 원본 이미지를 로드하고 lazy-img 클래스를 요소에서 제거하여 블러 효과를 없앤다. 이때 원본 이미지가 로드되기 전에 블러 효과가 제거되면 보기 좋지 않으므로 load 이벤트 리스너를 추가하여 원본 이미지 로드가 완료되었을 때 블러 효과를 제거하도록 하였다.
  4. 원본 이미지가 로드된 경우 해당 요소는 더 이상 감지하지 않는다 (unobserve)
// Lazy loading images
const imgTargets = document.querySelectorAll('img[data-src]');

const loadImg = function (entries, observer) {
  const [entry] = entries;

  if (!entry.isIntersecting) return;

  // Replace src with data-src
  entry.target.src = entry.target.dataset.src;

  entry.target.addEventListener('load', function () {
    entry.target.classList.remove('lazy-img');
  });

  observer.unobserve(entry.target);
};

const imgObserver = new IntersectionObserver(loadImg, {
  root: null,
  threshold: 0,
  rootMargin: '200px',
});

imgTargets.forEach(img => imgObserver.observe(img));

결과는 다음과 같다. 명확한 결과 확인을 위해 개발자 도구의 Network 탭에서 네트워크 환경을 요즘 네트워크 환경보다 조금 느린 Fast 3G로 설정하여 시뮬레이션하였다.

profile
기록하기

0개의 댓글