Javascript - lazy loading

웹 페이지의 초기 로딩 시간을 단축하고 웹 성능을 향상시키는 lazy loading

Posted by Yan on May 23, 2021

lazy loading이란

  • 모든 이미지들을 한번에 불러와서 로딩이 오래 걸리는 현상을 방지하고, 웹 성능을 향상시키기 위해 뷰포트에 아직 등장하지 않은 이미지는 로딩을 지연시켰다가 불러오는 방식이다.

  • 성능 향상과 비용 감소에 효과적이다.

구현 코드

아주 잘 설명된 글이 있어서 보고 따라서 코드를 쳐보면서 이해하려고 해봤다.
웹 성능 최적화를 위한 Image Lazy Loading 기법

먼저 아래 코드들의 html 코드는 이렇다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>

  <style>
    img {
      background: #f1f1fa;
      width: 400px;
      height: 300px;
      display: block;
      margin: 10px auto;
      border: 0;
    }
  </style>
  <script src="app.js"></script>
  <body>
    <img src="https://ik.imagekit.io/demo/img/image1.jpeg?tr=w-400,h-300" />
    <img src="https://ik.imagekit.io/demo/img/image2.jpeg?tr=w-400,h-300" />
    <img src="https://ik.imagekit.io/demo/img/image3.jpg?tr=w-400,h-300" />
    <img
      class="lazy"
      data-src="https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300"
    />
    <img
      class="lazy"
      data-src="https://ik.imagekit.io/demo/img/image5.jpeg?tr=w-400,h-300"
    />
    <img
      class="lazy"
      data-src="https://ik.imagekit.io/demo/img/image6.jpeg?tr=w-400,h-300"
    />
    <img
      class="lazy"
      data-src="https://ik.imagekit.io/demo/img/image7.jpeg?tr=w-400,h-300"
    />
    <img
      class="lazy"
      data-src="https://ik.imagekit.io/demo/img/image8.jpeg?tr=w-400,h-300"
    />
    <img
      class="lazy"
      data-src="https://ik.imagekit.io/demo/img/image9.jpeg?tr=w-400,h-300"
    />
    <img
      class="lazy"
      data-src="https://ik.imagekit.io/demo/img/image10.jpeg?tr=w-400,h-300"
    />
  </body>
</html>

자바스크립트 이벤트 이용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
document.addEventListener("DOMContentLoaded", () => {
  let lazyLoadImages = document.querySelectorAll("img.lazy");
  let lazyLoadThrottleTimeout;

  const lazyLoad = () => {
    if (lazyLoadThrottleTimeout) {
      clearTimeout(lazyLoadThrottleTimeout);
    }

    lazyLoadThrottleTimeout = setTimeout(() => {
      let scrollTop = window.pageYOffset;
      lazyLoadImages.forEach((img) => {
        if (img.offsetTop < window.innerHeight + scrollTop) {
          img.src = img.dataset.src;
          img.classList.remove("lazy");
        }
      });

      if (lazyLoadImages.length == 0) {
        document.removeEventListener("scroll", lazyLoad);
        window.removeEventListener("resize", lazyLoad);
        window.removeEventListener("orientationchange", lazyLoad);
      }
    }, 20);
  };

  document.addEventListener("scroll", lazyLoad);
  window.addEventListener("resize", lazyLoad);
  window.addEventListener("orientationchange", lazyLoad);
});

Intersection Observer API를 이용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
document.addEventListener("DOMContentLoaded", () => {
  let lazyLoadImages;

  if ("IntersectionObserver" in window) {
    lazyLoadImages = document.querySelectorAll(".lazy");
    let imageObserver = new IntersectionObserver((entries, observer) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          let image = entry.target;
          image.src = image.dataset.src;
          image.classList.remove("lazy");
          imageObserver.unobserve(image);
        }
      });
    });

    lazyLoadImages.forEach((image) => {
      imageObserver.observe(image);
    });
  } else {
    let lazyLoadThrottleTimeout;
    lazyLoadImages = document.querySelectorAll(".lazy");

    const lazyLoad = () => {
      if (lazyLoadThrottleTimeout) {
        clearTimeout(lazyLoadThrottleTimeout);
      }

      lazyLoadThrottleTimeout = setTimeout(() => {
        let scrollTop = window.pageYOffset;
        lazyLoadImages.forEach((img) => {
          if (img.offsetTop < window.innerHeight + scrollTop) {
            img.src = img.dataset.src;
            img.classList.remove("lazy");
          }
        });

        if (lazyLoadImages.length == 0) {
          document.removeEventListener("scroll", lazyLoad);
          window.removeEventListener("resize", lazyLoad);
          window.removeEventListener("orientationchange", lazyLoad);
        }
      }, 20);
    };
    document.addEventListener("scroll", lazyLoad);
    window.addEventListener("resize", lazyLoad);
    window.addEventListener("orientationchange", lazyLoad);
  }
});

Native Lazy Loading

1
2
<img src="example.jpg" loading="lazy" alt="..." />
<iframe src="example.html" loading="lazy"></iframe>

이제는 이미지 속성 중에 loading="lazy"가 생겼다고 한다.

  • lazy: 뷰포트에서 일정한 거리에 닿을 때까지 로딩을 지연
  • eager: 현재 페이지 위치가 위, 아래 어디에 위치하던 상관없이, 페이지가 로딩되자마자 해당 요소를 로딩
  • auto: 이 속성은 디폴트로 로딩을 지연하는 것을 트리거. 기본적으로 이것은 loading 속성을 쓰지 않을 것과 같다.

참고자료 Throttle이란