[Web.dev] Fast (4) - Lazy Loading

2022. 6. 1. 19:39Frontend

 

 

 

Overview

web.dev에서 소개하는 Web Performance에 대한 내용들을 여러 챕터에 걸쳐서 정리합니다. 모든 내용들을 다 다루지는 않고, 개인적으로 중요하다고 생각하는 부분들을 추려서 중점적으로 정리했습니다. 자세한 내용들은 아래 Table of Contents의 링크를 통해 확인하실 수 있습니다.

 

이미지 및 비디오 지연 로드

 

Use Lazy Loading to Improve Loading Speed

지연 로딩(이하 Lazy Loading) 페이지 로드 시 중요하지 않은 리소스의 로드를 연기하는 것을 의미합니다. 이렇게 지연된 리소스들은 "필요한 순간"에 로드됩니다. "필요한 순간"은 대체로 리소스가 사용자의 화면(viewport)에 보여져야 할 때를 의미하며 이는 "Intersection Observer"를 통해 구현 가능합니다.

 

이미지나 동영상등의 리소스들을 Lazy Loading하면 초기 페이지 로드 시간, 초기 페이지 번들 사이즈 및 시스템 리소스(CPU, Memory)의 사용량이 줄어들기 때문에 Web.dev에서는 Lazy Loading을 두고 "논쟁의 여지가 없는 합리적인 선택" 이라고 말하고 있습니다.

 

lazy loading과 더불어 가져오려고 하는 이미지의 용량이 큰 경우, 다음과 같이 이미지가 로딩되는 동안 serving 될  blur image를 추가할 수도 있습니다.

 

Lazy Loading Images

Intersection Observer

Intersection Observer API는 타겟 요소와 상위 요소 또는 최상위 document 의 viewport 사이의 intersection 내의 변화를 "비동기적"으로 관찰하는 방법입니다. 주로 Image Lazy Loading, Infinite Scroll 등을 메인스레드에 부담을 주지 않고 구현하기 위해 자주 사용됩니다. Intersection Observer가 "Scroll Event"에 비해 효율적으로 동작한다는 것에 대해서 MDN은 다음과 같이 이야기하고 있습니다. (브라우저가 Intersection Observer를 지원하지 않는 경우 setInterval을 사용하거나 scroll event를 사용해서 polyfill을 넣어줄 수 있습니다.)

 

Intersection Observer API 는 그들이 감시하고자 하는 요소가 다른 요소(viewport)에 들어가거나 나갈때 또는 요청한 부분만큼 두 요소의 교차부분이 변경될 때 마다 실행될 콜백 함수를 등록할 수 있게 합니다. 즉, 사이트는 요소의 교차를 지켜보기 위해 메인 스레드를 사용할 필요가 없어지고 브라우저는 원하는 대로 교차 영역 관리를 최적화 할 수 있습니다.

 

Intersection Observer가 없던 시절, intersection 감지를 구현하기 위해(Lazy Loading이나 infinite-scroll 등의 이유로) 브라우저의 Scroll Event를 사용하곤 했습니다. 즉 Scroll Event의 콜백으로 Element.getBoundingClientRect등과 같은 API를 호출해서 계산하는 식으로 작동하는 것이었는데, 이는 결국 콜 스택이 비었을 때 콜백함수가 콜 스택으로 올라와서 실행되는 방식이기 때문에 해당 콜백이 많아지게 될 경우, 메인 스레드를 오래 점유하게 되고 이는 퍼포먼스에 영향을 주게 됩니다.(getBoundingClientRect의 API가 연산량이 많은 API이기도 합니다.)

 

Intersection Observer는 이러한 문제를 다음과 같이 해결합니다.

 

  • Intersection Observer는 오로지 targetElement의 "Intersecting" 여부만 판단한다
  • Intersecting 상태인 경우에는 등록된 Callback을 실행한다.

 

https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model

 

 

w3c spec에 따르면, Intersecting 여부를 판단하는 시점은 "Update the Rendering" Step 중 animation frame callback 함수가 실행되고 난 직후에 일어난다고 합니다. 즉 화면이 리렌더링될 때, animation frame callback이 처리되고 나서 Intersecting 여부를 계산한 후, intersecting 상태인 element에 대해 callback을 등록해주는 것입니다. 즉, 별도의 메인스레드 Task를 통해 다시 pixel 정보를 계산하지 않고, repaint 과정에서 intersecting 여부만 다시 업데이트 하는 것이기 때문에 intersecting 여부를 판단하기 위해 별도도 메인 스레드를 사용할 필요가 없는 것입니다. 

An Intersection Observer processing step should take place during the "Update the rendering" steps,
after step 12, run the animation frame callbacks, in the in the HTML Processing Model.
This step is: For each fully active document in docs, 
Run the update intersection observations steps for that document,
passing in now as the timestamp.

 

 

 

Intersection Observer

Abstract This specification describes an API that can be used to understand the visibility and position of DOM elements ("targets") relative to a containing element or to the top-level viewport ("root"). The position is delivered asynchronously and is usef

w3c.github.io

 

Intersection Observer API - Web API | MDN

Intersection Observer API는 타겟 요소와 상위 요소 또는 최상위 document 의 viewport 사이의 intersection 내의 변화를 비동기적으로 관찰하는 방법입니다.Intersection Observer API는 타겟 요소와 상위 요소 또는

developer.mozilla.org

 

 

How Nextjs Handles Lazy Loading Images

Nextjs에서 제공하는 next/image를 사용해서 이미지를 최적화하는 경우 기본적으로 lazy loading 옵션이 제공됩니다.

 

next/image | Next.js

Enable Image Optimization with the built-in Image component.

nextjs.org

이때, Lazy Loading을 위해 실제로 이미지를 가져오는 시점을 늦추기 위해서 Nextjs는 내부적으로 Intersection Observer를 사용합니다. 아래 코드 예시와 같이, next/image를 사용할 때 주입하는 isLazy flag가 활성화되어 있고(next/image는 default loading option이 'lazy' 입니다) 해당 이미지가 유저의 viewport 에 보여져서 Intersection Observer가 'intersecting' flag를 true로 변경해주면, 이때 이미지를 보여주게 되는 것입니다.

const isVisible = !isLazy || isIntersected
...
if (isVisible) {
  imgAttributes = generateImgAttrs({
    config,
    src,
    unoptimized,
    layout,
    width: widthInt,
    quality: qualityInt,
    sizes,
    loader,
  })
}
 

GitHub - vercel/next.js: The React Framework

The React Framework. Contribute to vercel/next.js development by creating an account on GitHub.

github.com

 

 

위의 코드에서 'isIntersected' 라는 flag는 내부적으로 use-intersection이라는 Nextjs에서 정의한 custom-hook을 사용하는데, 만약 해당 코드가 돌아가는 브라우저가 Intersection Observer를 사용할 수 없는 상황이라면, 언제나 visible option을 true로 주어 항상 Lazy Loading이 적용되지 않고 Eager Loading을 하도록 처리합니다. 즉, next/image에서 lazy loading 옵션을 활성화하더라도, 브라우저가 Intersection Observer를 사용할 수 없는 환경인 경우에는 lazy loading이 적용되지 않는다는 것입니다.

const isDisabled: boolean = disabled || !hasIntersectionObserver
...
useEffect(() => {
  if (!hasIntersectionObserver) {
    if (!visible) {
      const idleCallback = requestIdleCallback(() => setVisible(true))
      return () => cancelIdleCallback(idleCallback)
    }
  }
}, [visible])

 

 

GitHub - vercel/next.js: The React Framework

The React Framework. Contribute to vercel/next.js development by creating an account on GitHub.

github.com

 

 

반응형