Welcome :っ)

Devlog/JavaScript

[JavaScript] Image Lazy Loading과 Intersection Observer API

lazy.won 2022. 8. 10. 18:20
728x90

 

 

 

 

 

 

 

 

 

Image Lazy Loading이란?

이미지 지연 로딩은 페이지 안에 있는 이미지들이 실제로 화면에 보여질 필요가 있을 때 로딩할 수 있도록 하는 기법이다. 

웹 페이지 내에서 바로 이미지를 로딩하지 않고 로딩 시점을 뒤로 미루는 것이라 볼 수 있다. 

 

Image Lazy Loading을 이용하면 다음과 같은 두 가지 이점이 있다. 

 

1. 성능 향상

페이지 초기 로딩 시 필요한 이미지의 수를 줄일 수 있다. 

리소스 요청을 줄이는 것은 다운로드 bytes를 줄이는 것이며, 이는 유저가 사용할 수 있는 제한된 네트워크 대역폭의 경쟁을 줄이는 것을 의미한다. 

디바이스가 다른 리소스들을 더 빨리 처리해서 다운로드하도록 하여 페이지를 훨씬 빨리 유저가 이용할 수 있도록 한다. 

 

2. 비용 감소

통신 비용 관점에 있다. 

lazy-loading은 이미지가 보여지지 않으면 절대 로딩하지 않으므로, 페이지 내에서 전달할 총 바이트 용량을 줄일 수 있다. 

특히 페이지 이탈하거나 페이지 제일 상단에만 서비스를 이용하는 유저들에게 효과적이다. 

그래서 네트워크로부터 전송될 바이트 감소는 전송 비용을 줄인다. 

 

 

💡 <img> 태그를 이용한 방법

먼저, 이미지 로딩을 사전에 막는 방법이 있다. 

일반적으로 <img> 태그를 이용해서 이미지를 로드하기 위해, 브라우저는 태그 내 src 속성을 이용한다. 

만약 브라우저가 src 속성을 가지면, 이미지를 무조건 로드한다. 

 

이미지들의 로딩을 지연시키려면, src 속성 대신 data-src 속성에 이미지 URL을 지정하면 된다. 그러면 src는 비워져 있고, 브라우저는 해당 이미지를 로드하지 않게 된다. 

 

위 방식으로 이미지 로드를 사전에 막고, 브라우저에게 해당 이미지를 언제 로딩할 것인지 알려주어 그때에 src 속성에 URL을 지정해준다. 

 

상단 처음 이미지 같은 경우, 미리 로딩되도록 src 속성으로 URL 적용해두는 것이 UX 상 좋다. 페이지 상단에 있는 이미지들은 JS 파일이 로딩되고 이벤트가 발생할 때까지 기다릴 수 없으므로, 가능한 빨리 보여지게 하는 것이 좋다. 

 

 

 

 

Intersection Obsever API

Intersection Observer API는 타겟 요소와 상위 요소 또는 최상위 document 의 viewport  사이의 intersection 내의 변화를 비동기적으로 관찰하는 방법입니다.
(https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API)

 

쉼게 말해, 이 API는 타겟 요소가 뷰포트에 들어가는 것을 감지하고 액션을 취하는 것을 간단하게 만들어 주는 것이다. 

 

 

💡 intersection 정보가 필요한 이유

  • 페이지가 스크롤 시 이미지 Lazy-loading(지연 로딩)할 때 사용
  • Infinite scrolling을 통해 스크롤 시 새로운 콘텐츠를 불러올 때 사용
  • 광고 수익을 계산하기 위한 용도로 광고의 가시성 보고
  • 사용자에게 결과가 표시되는 여부에 따라 작업이나 애니메이션을 수행할 지 여부 결정

 

기존에 특정 지점을 관찰하기 위해서는 getBoundingClientRect() 함수를 사용했다. 이 함수는 브라우저가 웹 페이지 일부 또는 전체를 다시 그려야 하는 reflow 현상이 발생한다는 단점이 있다. 

 

하지만, Intersection Observer API를 이용하면 위의 문제를 해결할 수 있다. 

비동기적으로 실행되기 때문에 메인 스레드에 영향을 주지 않으면서 변경 사항을 관찰할 수 있으며, IntersectionObserverEntry 속성을 활용하면 getBoundingClientRect()를 호출한 것과 같은 결과를 알 수 있어 따로 getBoundingClientRect() 함수를 호출할 필요가 없어 리플로우 현상도 방지할 수 있다.

 

 

 

 

Intersection Observer API를 이용한 이미지 Lazy Load 구현

이미지 로드를 지연시키기 위해 모든 이미지에 옵저버를 부착시킨다.

엘리먼트가 뷰포트에 들어간 것을 API가 감지했을 때, isIntersecting 속성을 이용해 URL을 data-src 속성에서 src 속성으로 이동시켜 브라우저가 이미지를 로드하도록 트리거를 일으키면 된다. 

 

 

예시를 한 번 보도록 하자.

const options = {
  root: null,
  rootMargin: "0px 0px 30px 0px",
  threshold: 0,
};

// IntersectionObserver 를 등록한다.
const io = new IntersectionObserver((entries, observer) => {
  entries.forEach((entry) => {
    // 관찰 대상이 viewport 안에 들어온 경우 image 로드
    if (entry.isIntersecting) {
      console.log(`entry: ${entry}`);
      // data-set 정보를 타겟의 src 속성에 설정
      entry.target.src = entry.target.dataset.src;
      // image 클래스 제거
      entry.target.classList.remove("image");
      // 이미지를 불러왔다면 타겟 엘리먼트에 대한 관찰을 멈춘다. 
      observer.unobserve(entry.target);
    }
  });
}, options);

// 관찰할 대상을 선언하고, 해당 속성을 관찰시킨다.
const images = document.querySelectorAll(".image");
images.forEach((el) => {
  io.observe(el);
});

 

entries는 IntersectionObserverEntry 인스턴스의 배열이다. 아래의 속성들을 포함하고 있다. 

IntersectionObserverEntry 구조

 

options

  • root : 타겟의 가시성을 검사하기 이해 뷰포트 대신 사용할 요소 객체(루트 요소)를 지정한다. 지정하지 않거나 null인 경우 브라우저의 뷰포트가 된다. 
  • rootMargin: margin을 이용해 Root 범위를 확장하거나 축소할 수 있다. 
  • threshold: 타겟의 가시성이 얼마나 필요한지 백분율로 표시하는 것이다. 

threshold: 0
threshold: 0.3

 

 

 

 

 

 

❗ 하지만, Intersection Observer API는 아직 모든 브라우저가 지원되고 있지는 않다. 

그래도 IE를 제외하곤 거의 모든 브라우저에서 지원되고 있어(전체 브라우저에서 약 96%가 지원 하고 있음), 해당 API가 지원되지 않는 브라우저에서는 event listener 방식으로 사용하도록 하는 방법이 있다. 

 

https://caniuse.com/?search=IntersectionObserver

 

 

 

 

 

 

 

실무 적용 및 성능 측정 결과

✨ 실무에 적용해 보기 

실제로 이미지를 많이 사용하고 있고 스크롤 이벤트 처리가 많은 실무 웹 페이지에 스크롤 이벤트 최적화와 함께 적용한 후,

Google lighthouse를 이용하여 성능 측정해 본 결과는 아래와 같다.

 

 

브라우저 테스트 환경 
- Chrome 개발자도구의 Lighthouse를 통해 성능 측정 
- 브라우저 캐시 비활성화 (크롬 개발자 도구의 Network 탭 > Disable cache 설정)
- 제한된 네트워크 설정 (크롬 개발자 도구의 Network 탭 > Disable cache 설정 우측에 있는 Throttling 옵션 선택하여 느린 인터넷 환경 속도로 설정 - Fast 3G 선택)
- 쿠키 및 사이트 데이터 사용하지 않기 위해 Chrome Secret Mode로 접속 후 테스트 

 

 

적용 전
적용 후

Idle은 웹 페이지가 최소한으로 상호 작용할 수 있는 상태가 될 때까지 걸리는 시간인데, 확실히 줄어든 것을 볼 수 있다. 

 

before
after

 

before
after

 

🎇 성능 측정 결과

  • FCP
    • 페이지 로드가 시작된 후 뷰포트 내 의미있는 콘텐츠 일부가 처음 화면에 렌더링 될 때까지의 시간
    • 2.0s → 1.1s
  • Speed Index
    • 뷰포트 내 콘텐츠가 눈에 띄게 채워지는 속도. Lighthouse 기준 3.4초 이내로 들면 빠른 편이라고 한다. (참고)
    • 4.2s → 2.8s
  • LCP
    • 페이지에서 가장 용량이 큰 컨텐츠가 표시되는 시점으로, LCP를 기준으로 사용자 중심의 페이지 로드 속도를 판단한다.
    • 15.3s → 6.9s (15초 대에서 6초 대로 줄다니..😲 하지만 Lighthouse 기준 LCP는 4초내로 들어야 양호한 편이며, 2.5초 내로 들어야 빠른 편으로 본다고 한다. 아직 갈 길이 멀었다.. )
    • 두 배 이상 빨라졌지만, 이미지 파일에 대한 최적화가 더 필요해 보인다. 더 고민해 보아야 겠다.
  • TTI
    • 페이지가 완전히 사용자와 상호 작용할 수 있는 상태가 되는데 걸리는 시간
    • 2.0s → 1.1s
  • CLS
    • 사용자가 예상하지 못한 레이아웃을 경험하는 빈도를 정량화해서 시각적인 안정성을 판단하는 기준
    • 0.933 → 0.091

 

 

 

이미지 지연 로딩과 스크롤 이벤트 최적화를 통해 웹 페이지 성능이 확실히 개선된 것을 볼 수 있다. 

더 개선할 수 있는 방법을 찾아 적용해 보아야 겠다.

 

 

 

 

 

 

 

 

참고

https://helloinyong.tistory.com/297

 

웹 성능 최적화를 위한 Image Lazy Loading 기법

현재 화면에 보여지지 않는 lazy loading된 이미지들은 웹 페이지 초기의 로딩 시간을 단축하여 웹 성능을 향상시킵니다. 이 글은 lazy loading 처리 기법과 관련된 모든 것들을 깊게 다루게 됩니다. 해

helloinyong.tistory.com

http://blog.hyeyoonjung.com/2019/01/09/intersectionobserver-tutorial/

 

Intersection Observer API의 사용법과 활용방법 · Yoon's devlog

Intersection Observer API의 사용법과 활용방법 Web API 중 하나인 Intersection Observer API를 알아보고 어떻게 활용할 수 있는지에 대해 정리한 글입니다. Intersection Observer API(교차 관찰자 API)를 들어본 적이

blog.hyeyoonjung.com

https://heropy.blog/2019/10/27/intersection-observer/

 

Intersection Observer - 요소의 가시성 관찰

Intersection observer는 기본적으로 브라우저 뷰포트(Viewport)와 설정한 요소(Element)의 교차점을 관찰하며, 요소가 뷰포트에 포함되는지 포함되지 않는지, 더 쉽게는 사용자 화면에 지금 보이는 요소인

heropy.blog

https://fe-developers.kakaoent.com/2022/220120-ux-and-perf-in-kakaowebtoon/

320x100