Lazy Loading...

Eddy·2023년 4월 11일
0

Javascript

목록 보기
14/27

Image Lazy Loading...


현재 화면에 보여지지 않는 이미지들은 lazy loading 처리를 통해 웹 페이지 초기의 로딩 시간을 단축하여 웹 성능을 향상 시킬 수 있다. image lazy loading 기법은 무엇인지, 그리고 해당 기법을 IntersectionObserver API를 사용한 예시를 보면서 알아보자.


Image Lazy Loading이란?

페이지 안에 있는 실제 이미지들이 실제로 화면에 보여질 필요가 있을 때 로딩을 하게 만드는 기법
웹 페이지 내에서 바로 로딩을 하지 않고 로딩 시점을 뒤로 미룬다
영어 단어 중 'lazy'는 '가능한 길게 일을 미루는 행위'란 의미에 기반한다.

lazy loading을 다루는 방식은 페이지 내의 거의 모든 리소스에도 적용할 수 있다. 예를 들어, SPA 내에서 JS 파일이 당장 필요하지 않으면 초기에 로드해서 가져오지 않는 것이 좋다. 이처럼 이미지도 당장은 불러오지 않다가, 보여져야 할 때 로딩을 하는 방식이다.


그래서 무엇을 얻을 수 있을까?

image lazy loading은 페이지 내에서 당장은 필요하지 않은 이미지들의 로딩 시점을 뒤로 미루는 것이다. 뷰포트에서 보여지지 않는 이미지들은 스크롤 등으로 실제로 이미지가 보여지는 시점이 올 때 로딩이 된다. 만약 스크롤 등의 동작을 하지 않는다면 보여지지 않은 이미지는 절대 로딩이 되지 않는다.

이러한 점은 2가지를 기대할 수 있다.

1. 성능 향상

페이지의 초기 로딩 시 필요한 이미지의 수를 줄일 수 있다. 이렇게 리소스 요청을 줄이는 것은 다운로드 용량을 줄이는 것이며, 이는 사용자가 사용할 수 있는 제한된 네트워크 대역폭의 경쟁을 줄이는 것을 의미한다. 또한 다른 리소스들을 더 빠르게 처리할 수 있게 도와줄 수 있다. 따라서 lazy loading을 쓰지 않는 것에 비해 사용자가 페이지를 더 빠르게 볼 수 있도록 돕는다.

2. 비용 감소

두 번째로 통신 비용 관점의 장점이 있다. 이미지 전달 또는 다른 리소스를 전달한다는 것은 주로 전송 바이트 수에 기반하여 청구된다.

image lazy loading은 이미지가 보여지지 않으면 절대 로딩하지 않기 때문에 페이지 내에서 전달할 총 바이트 용량을 줄일 수 있다. 특히나 페이지를 이탈하거나 페이지의 최상단에만 서비스를 이용하는 사용자에게 효과적이다. 이처럼 네트워크로부터 전송될 바이트의 감소는 전송 비용을 줄일 수 있다.


적용할 수 있는 이미지는?

lazy loading의 기본적인 방법은 간단하다. 지금 당장 필요하지 않은 모든 요소들의 로딩을 잠시 지연하는 것이다. 따라서 사용자 화면에 보여지지 않는 이미지들은 모두 로딩을 지연시킬 수 있다.

사용자가 페이지에서 스크롤을 아래로 내림으로써, 이미지의 placeholder는 뷰포트(웹 페이지 내 보여지는 부분)에 다가오게 된다. 뷰포트에 보여지게 되면 이미지를 로딩하도록 트리거를 일으킨다.

image lazy loading에 도움이 되는 사이트는 다음과 같다.

Google Lighthouse audit toll

  • lazy loading에 어떤 이미지가 적합한지 알 수 있다.
  • 페이지 초기 로딩 시 얼마나 바이트 용량을 줄일 수 있는지 알 수 있다.
    ImageKit's website analyzer
    lazy loading 사용 여부를 확인 할 수 있다.
    페이지 내에서 크리티컬한 이미지 관련 최적화 용도로 사용할 수 있다.

좋은 퍼포먼스 뿐만 아니라 서비스 사용성에서도 효과적인 image lazy loading을 적용해보자.


<img> 태그를 이용한 일반적인 방법

lazy loading 이미지들은 두 단계로 나눌 수 있다.

우선 첫 번째로 이미지 로딩을 사전에 막아야 한다. 일반적으로 브라우저는 <img> 태그를 이용해서 이미지를 로드하기 위해 태그 내 src 속성을 사용한다. HTML 내 첫 번째 이미지든 1000번째 이미지든, 또는 뷰포트 밖에 있든 상관없이 만약 브라우저가 src 속성을 갖는다면 이미지를 무조건 로드한다.

따라서 이와 같은 이미지들의 로딩을 지연시키기 위해 src 속성 대신 다른 속성에 이미지 URL을 넣어야 한다. 그 방법은 다음과 같다.

<img data-src="https://onlydev.tistory.com/demo/default-image.jpg" />

위 방식으로 이미지 로드를 사전에 막고 브라우저에게 해당 이미지를 언제 로딩할 것인지 알려주어야 한다. 이를 위해서 해당 이미지가 뷰포트에 들어오자마자 로딩할 수 있도록 확인해야 하는데 이를 IntersectionObserver API를 통해 구현이 가능하다.


IntersectionObserver API를 사용하여 lazy load를 구현한 예시

lazy load 시킨 이미지들을 로딩시키기 위해 해당 이미지들에 옵저버를 적용한다. 엘리먼트가 뷰포트에 들어간 것을 확인하기 위해 isIntersecting 그리고 intersectionRatio 속성을 사용한다. 또한 확인이 되면 URL을 data-src 속성에서 src 속성으로 옮겨서 브라우저가 이미지를 로드하도록 트리거를 일으킨다. 그 후 해당 이미지는 lazy 클래스를 제거하고 옵저버를 해제한다.

const useLazyLoading = () => {
  const imgs = document.querySelectorAll('.lazy');

  const observerCallback = (entries, observer) => {
    entries.forEach(({ isIntersecting, intersectionRatio, target }) => {
      if (isIntersecting && intersectionRatio > 0) {
        target.src = target.dataset.src;
        target.classList.remove("lazy");
        observer.unobserve(target);
      }
    });
  };

  const io = new IntersectionObserver(observerCallback);
  imgs.forEach((img) => io.observe(img));
};

useLazyLoading();

구현된 코드


lazy loading 기법으로 유저 인터페이스 향상시키는 방법

Lazy loading은 뛰어난 성능이라는 장점이 있습니다. 페이지 내에서 몇 백개의 상품 이미지를 쓰는 E-커머스 기업의 경우, lazy loading 방식은 페이지의 초기 로딩에 엄청나게 성능을 향상시킬 수 있을 것입니다.

하지만, 거의 대부분 기업에서는 초기 placeholder가 보기 좋지 않다던가, 로딩 시간이 느리다 와 같은 여러 가지 이유로 유저 사용성에 좋지 않다고 판단하여 lazy loading을 쓰지 않습니다.

그렇다면 이러한 lazy loading에 대한 사용성 문제에 대해 어떻게 해결할 수 있을까요?

1. 올바른 image placeholder를 이용한 방법

placeholder는 실제 이미지가 로딩될 때까지 해당 이미지 자리에 대신 표시할 요소를 말합니다. 보통 개발자들은 이미지의 단색 이미지를 사용하거나 모든 이미지에다가 대체할 특정 이미지를 넣는 방식으로 구현합니다.

이전 예시 코드로 동작하던 lazy code를 본다면, placeholder로 밝은 회색을 사용하고 있습니다. 그러나 이 부분을 유저 사용성에 더 좋게 만들 수 있습니다.

그러면 아래에서 placeholder를 더 좋게 만드는 예시를 확인해봅시다.

a) 주요 색상을 placeholder로 사용하기
특정 색상을 placeholder로 사용하는 대신, 사용하려는 실제 이미지의 주요한 색상을 이용하는 방법이 있습니다.

이 방식은 구글 이미지 검색 결과와 Pinterest에서 꽤 오래 사용되어 왔습니다.

아래는 Manu.ninja에서 소개하고 있는 예시입니다.

처음에는 이를 구현하기 어려워 보이겠지만, 단지 이미지의 첫 1x1 픽셀로 스케일을 감소 시키고 해당 픽셀 요소로 placeholder을 채우는 아주 간단한 방식입니다. 하지만 해당 이미지의 단일 색상을 추출하는 것은 약간 번거로울 수도 있습니다.

ImageKit을 사용하면, 체인변환을 이용해서 주요 단일 색상을 얻을 수 있습니다. 아래에서 ImageKit에 대해 확인해봅시다.

ImageKit을 이용한 이미지의 주요 단색의 URL을 얻는 예시

original image dominant color placeholder
<!-- Original image at 400x300 -->
<img src="https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300" alt="original image" />

<!-- Dominant colour image with same dimensions -->
<img src="https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-1,h-1:w-400,h-300" alt="dominant color placeholder" />

placeholder 이미지는 단지 661 바이트의 용량을 가집니다. 원본 이미지의 12700바이트의 용량과 비교해서 약 19배 단축되었습니다. 뿐만 아니라 실제 이미지의 placeholder로부터 사용성 면으로 더욱 좋게 변했습니다.

위 샘플 영상에 대한 코드 샘플은 여기서 확인할 수 있습니다.

b) 저화질의 이미지로 placeholder로 사용하기(LQIP)
앞 단의 방식인 주요 색상 placeholder를 이용해서 더 좋은 방식으로 확장시킬 수 있을 것 같습니다.

하나의 색상을 사용하는 대신, 원본 이미지에 대한 저화질의 흐린 이미지를 placeholder로 사용하는 것입니다. 이는 이미지가 로딩되는 동안 유저가 보기에 이전보다 더 보기 좋을 뿐 아니라 원본 이미지에 대해 어느정도 추측하도록 유도할 수 있습니다.

이 방식은 Facebook과 Medium.com 같은 웹 사이트와 어플리케이션에서 사용되어 왔습니다.

아래는 ImageKit을 이용해서 LQIP 이미지 URL을 적용한 예시 입니다.

original image dominant color placeholder
<!-- Original image at 400x300 -->
<img src="https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300" alt="original image" />

<!-- Low quality image placeholder with same dimensions -->
<img src="https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300,bl-30,q-50" alt="dominant color placeholder" />

LQIP(저화질 이미지)는 1300바이트의 크기를 가지므로, 원본 이미지에서 약 10배 정도 감소된 사이즈입니다. 그리고 다른 placeholder 기술보다 시각적인 면에서 더 좋은 사용성을 제공합니다.

앞서 본 주요 색상을 이용하는 방식, 저화질 이미지를 이용하는 방식은 비디오 샘플에서 본 것처럼, 유저가 placeholder를 봤을 때 로딩 중인 것을 인지시키면서 스무스하게 원본 이미지로 변환시킬 수 있는 것을 확인할 수 있었습니다.

2. 이미지 로딩을 위한 버퍼 시간을 추가하는 방법

앞서 이미지를 로딩하는 트리거에 대한 차이점을 논의할 때, 뷰포트의 하단 부분과 이미지의 상단 부분이 만나는 시점이 뷰포트에 들어오는 시점인 것을 확인할 수 있었습니다.

문제점

사용자가 페이지에서 종종 빠르게 스크롤하면 이미지가 로딩될 시간이 필요하단 것이 placeholder로 보여지게 되는 것입니다. 이것은 이미지를 로딩하는 스크롤 이벤트를 성능 이슈로 인해 쓰로틀링을 사용해서 딜레이가 약간 발생하는 것입니다. 이로인해 placeholder가 뷰포트에 오게되면 유저는 이미지가 로딩되는 약간의 시간을 기다리게 됩니다. 이러한 딜레이는 유저 사용성에서 좋지 않을 수 있습니다.

Intersection Observer 또는 저화질의 이미지를 이용한 방식으로 placeholder의 퍼포먼스를 더 좋게 만들면서도, 더 나아가 간단한 트릭으로 이미지가 뷰 포트에 들어올 때 완벽하게 이미지가 로딩하도록 할 수 있습니다. 그것은 바로 이미지의 로딩할 트리거 시점에 마진 값을 넣는 것입니다.

해결 방법

이미지가 뷰포트에 완전히 들어올 때 로딩하는 대신, 뷰포트에 들어오는 부분에서 예시로 500px정도 떨어진 곳에 들어오면 로딩을 하는 것입니다. 이 방식은 실제로 이미지가 뷰포트에 들어왔을 때 완전히 로딩되어 보여지기 위해 미리 로딩 트리거 시점을 더 빨리 잡도록 하는 것입니다.

Intersection Observer API에서는 'root'파라미터와 'rootMargin'파라미터(기본 CSS 마진 규칙에서 동작)를 함께 사용하여 'intersection'(이벤트가 발생되는 부분)을 찾는 위치를 증가시킬 수 있습니다.

이벤트 리스너 방식에서는 이미지 위치와 뷰 포트 위치의 간격을 체크하는 대신에 어느 정도의 임계 값을 추가하는 방식을 이용할 수 있습니다.

더 자세히 알고 싶다면 여기서 500px의 임계값을 이용한 예시를 확인해보세요.

아래 비디오(특히 개발자 도구의 네트워크 요청 탭을 주목)에서 정확히 알 수 있듯이, 스크롤 하는 동안, 상단 3개의 이미지는 바로 보여질 때 5개의 이미지가 로딩이 됩니다. 또한 4개의 이미지가 보여졌을 때, 6개의 이미지가 로딩됩니다. 이 방법은 이미지가 완벽히 로딩되도록 충분히 시간을 가지게 합니다. 이로 인해 사용자는 거의 placeholder를 보지 않게 될 것입니다.

모든 예시에서 보면, 상단 3개의 이미지는 뷰포트에 들어와 있지 않았어도 항상 미리 로드 되어있습니다. 이것은 더 나은 사용성을 위해 threshold에 완전히 로딩되는 대신 미리 약간 로딩하는 방식과 동일하게 사용됩니다.

3. lazy loading으로 콘텐트 요소들이 이동하는 것을 방지하는 방법

이것은 만약 해결된다면 유저 사용성을 조금 더 좋게 할 수 있는 아주 사소한 부분입니다.

문제점

이미지가 없을 때, 브라우저는 보여지고 있는 콘텐트의 공간을 알고있지 않습니다. 그래서 만약 CSS로 따로 지정해주지 않으면, 콘텐트를 감싸고 있던 컨테이너 부분은 공간을 따로 가지고 있지 않음으로 0x0 픽셀이 됩니다. 따라서 이미지가 로딩되면, 브라우저가 해당 이미지의 크기에 맞게 컨테이너를 다시 리사이징합니다.

이러한 갑작스런 변경은 다른 엘리먼트 요소들이 이동되어 밀려나게 되는 원인으로 인해 일어나게 됩니다. 이 부분은 Smashing 매거진의 content shifting article & video 에서 정확히 알 수 있으며, 이는 이미지가 로딩될 때 갑작스런 콘텐트 이동으로 인해 꽤나 유저에게 좋지 않은 사용성을 주게 됩니다.

해결 방법

콘텐트를 감싸고 있는 컨테이너에 너비/높이를 지정함으로써 이러한 문제를 피할 수 있습니다. 이로 인해 브라우저는 너비와 높이를 가지고 이미지 영역을 알 수 있습니다. 이후 이미지가 로딩되면 이미 이미지 사이즈 영역이 이미지 크기에 완벽히 맞게 지정되어 있으므로, 나머지 컨테이너 영역들은 그대로 유지됩니다.

4. 모든 이미지에 lazy load를 적용하지 않기

이것은 개발자들이 종종 실수하는 부분인데, 페이지 내 사용되는 모든 이미지에 lazy load를 하도록 구현합니다. 초기 로딩 시에는 로딩이 줄어들기는 하지만, 많은 이미지들로 인해 유저 사용성으로 좋지 않은 결과가 주어질 수도 있습니다. 특히나 웹 페이지 상단의 이미지는 자바스크립트가 적용되고 동작할 때까지 보여지지 않을 것입니다.

이를 위해, 아래에는 lazy loading에 어울리는 이미지가 무엇인지 식별할 수 있는 일반적인 원칙입니다.

a) 뷰포트에 있거나, 웹 페이지에서 시작되는 이미지들은 lazy loading을 하도록 하면 안됩니다. 페이지 상단 이미지나 마케팅 배너, 로고 등과 같은 이미지는 페이지가 로딩 되자마자 유저에게 보여져야 합니다.

또한, 모바일과 데스크톱에는 화면 크기가 다르게 때문에, 초기에 화면에 보여지는 이미지 수가 다릅니다. 따라서 어떤 리소스들을 미리 로딩할 것인지 결정하기 위해 디바이스 타입을 고려할 필요가 있습니다.

b) 뷰포트에서 살짝 떨어져 있는 이미지는 lazy loading할 이미지로 적합하지 않습니다. 이것은 앞 단에서 뷰포트에 완벽히 들어서기 전에 미리 로딩을 하는 부분을 다루던 내용에 기반한다면, 뷰포트 하단으로부터 500px 이내인 이미지들은 미리 로딩되어야 하는 것을 예시로 들 수 있습니다.

c) 만약 페이지가 길지 않다면, 한, 두번 정도의 스크롤로 페이지를 내리거나 뷰포트 바깥에 이미지 개수가 5개 이하일 것입니다. 이럴 땐 lay loading을 전부 걸지 않는게 더 좋을 수 있습니다.

성능 면으로 최종적으로 유저에게 중요한 장점을 제공하지 않을 수도 있습니다. lazy loading를 걸기위해 추가한 JS파일이 고작 몇 안되는 이미지 수의 lazy loading을 위해서 오히려 장점이 사라질 수도 있습니다.


lazy loading을 테스트 하는 방법

lazy loading을 구현했다면, 웹 사이트 내에서 이미지들이 의도한대로 동작하는지 확인하기를 원할 것입니다. 간단한 방법은 크롬 브라우저의 개발자 툴을 이용하는 것입니다.

브라우저 내 네트워크 탭 -> 이미지를 확인하십시오.

여기에서 처음으로 페이지를 새로고침 하면, 미리 로딩되었던 이미지들이 다시 새롭게 로딩이 될 것입니다. 그러면 페이지 내 스크롤을 아래로 내림으로써, 다른 이미지들이 로드 요청 트리거가 일어나면서 이미지가 로딩될 것입니다.

화면 내 waterfall column에서 이미지의 로딩 타이밍도 알 수 있습니다. 이것은 이미지 로딩 트리거 관련 이슈사항이 있을 경우, 각 이미지 로딩에 대해 식별하는 것에 도움을 줄 것입니다.


lazy loading과 관련해 인기있는 자바스크리브 라이브러리 소개

브라우저 환경과 상세하게 구현하는 것은 브라우저와 디바이스마다 다를 수 있으므로, lazy loading 기능 그리고 관련 테스트 라이브러리를 이용할 수 있습니다.

아래는 이와 관련된 인기있는 라이브러리와 특정 플러그인입니다. 이것들은 최소한의 노력으로 lazy loading을 구현할 수 있습니다.

yall.js (또 다른 Lazy Loader 구현 방법)
Intersection Observer 방식이며, 이벤트 기반으로 lazy loading을 되돌리는 방식.
주요한 HTML 요소들은 모두 지원하지만 background-image는 안됨.
게다가 브라우저 IE11도 지원됨.

lazysizes
엄청 인기있고 광범위한 기능
src 이미지 응답이 지원되고 속성 사이즈 지정 가능
Intersection Observer 없는 좋은 성능

JQuery Lazy
jquery 기반 lazy loading을 구현할 수 있는 간단한 라이브러리

WeltPixel Lazy Loading Enhanced
lazy loading을 위한 Magento 2 확장판


참고자료

보다 다양한 레이지 로딩 기법
yall.js

0개의 댓글