블러 placeholder를 사용한 Lazy Loading(GIF 화질 때문에 블러 이미지가 뭉개져서 보이는 점 참고)

블러 placeholder를 사용한 Lazy Loading(GIF 화질 때문에 블러 이미지가 뭉개져서 보이는 점 참고)

웹페이지에서 성능에 영향을 가장 많이 주는 부분이 이미지 / 비디오 같은 미디어 요소다. 특히 이미지는 배너, 제품 사진, 로고 등 페이지 구석구석에서 사용한다. HTTP Archive Data에 따르면 전체 웹페이지 용량의 45%를 이미지가 차지한다고 한다. 이미지를 사용하지 않는건 불가능하지만, 화면에 노출될 때만 이미지를 불러오는 방식으로 페이지 로딩 시간을 단축시킬 수 있다. 이런 방식을 Lazy Loading이라고 한다.

Lazy Loading 구현 방법


<aside> 💡 Lazy Loading이 적용된 이미지가 뷰포트에 근접해서 이미지를 로드하면 콘텐츠가 밀려나는 현상이 발생한다. 이를 방지하려면 이미지를 감싸는 컨테이너 요소에 높이 / 너비를 지정하면 된다.

</aside>

Lazy Loading은 크게 Chrome Native 방식과 JavaScript를 이용한 방식으로 구현할 수 있다.

(1) Native Lazy Loading 방식

Chrome 76 버전 이상부터 Native 방식의 Lazy Loading을 지원한다. 적용할 이미지 태그의 loading 속성에 lazy만 추가하면 된다. 사용법은 간단하지만 76버전 이상의 크롬 브라우저에서만 적용되는 단점이 있다.

<img src="example.jpg" loading="lazy" width="200" height="200" alt="..." />

(2) JavaScript 활용 방식

이미지 태그의 src 속성엔 로딩 전 보여질 placeholder 이미지를 추가해두고, 데이터 프로퍼티를 이용해 원본 이미지를 data-src 속성에 할당해둔다. 이미지 태그가 화면에 노출되면 data-src 속성에 지정해놓은 원본 이미지를 src 속성에 할당해서 원본 이미지를 로드한다.

<!-- 화면 노출 전 -->
<img data-src="원본 이미지 주소" src="placeholder 이미지 주소" />

<!-- 화면 노출 후 -->
<img data-src="원본 이미지 주소" src="원본 이미지 주소" />

위 방식을 구현하려면 이미지 태그의 화면 노출 여부를 확인해야된다. 구현 방법은 아래 2가지가 있다.

Scroll 이벤트 + 기하 프로퍼티 활용

스크롤 이벤트가 발생할 때마다 요소가 화면에 노출됐는지 확인하는 방식. getBoundingClientRect 메서드를 호출해서 뷰포트 기준의 요소 위치 좌표값을 얻어야 한다. 이 메서드를 호출할 때마다 리플로우가 발생하는 단점이 있으며, 마우스를 스크롤할 때마다 이벤트가 계속 호출되므로 스로틀도 적용해야 된다.

// 요소가 화면에 완전히 들어왔는지 확인하는 함수
function checkInView(el) {
  // 주어진 요소의 뷰포트 기준 위치 및 크기 반환
  const rect = el.getBoundingClientRect();
  return (
    rect.top >= 0 && // 뷰포트 상단보다 아래 있는지 여부
    rect.left >= 0 && // 뷰포트 왼쪽보다 오른쪽에 있는지 여부
    rect.bottom <= window.innerHeight && // 뷰포트 하단 이내에 있는지 여부
    rect.right <= window.innerWidth // 뷰포트 오른쪽 이내에 있는지 여부
  );
}

window.addEventListener('scroll', () => {
  document.querySelectorAll('.lazy-img').forEach((image) => {
    if (checkInView(image)) {
      image.src = image.dataset.src; // 이미지 태그가 화면에 노출되면 src 속성에 원본주소 할당
    }
  });
});