[ 기술 스터디 ] Lazy-loading이란 무엇인가.

김민석·2021년 7월 11일
5

기술 스터디

목록 보기
11/18

참고 :


들어가기 전에

Lazy-loading 없이 구현된 웹 페이지를 사용자가 접근하면 그 안에 있는 내용이 모두 다운로드 된다.
만약 사용자가 웹 페이지의 모든 컨텐츠를 실제 이용한다면 첫 로딩이 조금 느리더라도 문제가 크진 않다.
하지만 만약 최상단의 이미지만 확인하고 나가버리면 낭비가 발생한다.

이런 문제를 해결하기 위해 lazy loading 기법이 나왔다.


LazyLoading의 정의

정의 :

페이지를 불러오는 시점에 당장 필요하지 않은 리소스들을 추후에 로딩하게 하는 기술

즉, 사용자가 보지 않는 것들을 당장 로딩하지 않는다.
그러다가 나중에 사용자가 필요로 하는 시점에 로딩하는 것이다.


예시

우선 무한 스크롤과 placeholder가 Lazy Loading의 예시다.

전자인 무한 스크롤은 사용자의 스크롤 깊이를 계산하고 있다가, 페이지 끝에 다달았을 때, 데이터를 요청하는 방식으로 구현하게 된다.

무한 스크롤의 단순화 예시

useEffect(() => {
		const onScroll = () => {
			if ( "document 높이" - 300 < "view port 높이" + "스크롤 깊이" ) {
					"server에 요청을 보냄"
				}
			}
		};
         
		window.addEventListener('scroll', onScroll);
		return () => {
			window.removeEventListener('scroll', onScroll);
		};
	}, [...]);

후자인 placeholder를 이용하는 lazyloading 중 가장 흔항 방식은 <img>태그의 속성을 이용하는 것이다.


<img> 태그를 활용한 lazyloading의 예시

<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" />
document.addEventListener("DOMContentLoaded", function() {
  var lazyloadImages = document.querySelectorAll("img.lazy");    
  var lazyloadThrottleTimeout;

  function lazyload () {
    if(lazyloadThrottleTimeout) {
      clearTimeout(lazyloadThrottleTimeout);
    }    
 
    lazyloadThrottleTimeout = setTimeout(function() {
        var scrollTop = window.pageYOffset;
        lazyloadImages.forEach(function(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);
});

코드 출처

위의 코드는 복잡해보이지만 사실은 간단하다.
1. 우선 scroll event를 그대로 들으면 성능상의 문제가 있기 때문에 throttle을 setTimeOut을 이용해서 구현했다.
2. class에서 lazy를 걷어낸다.
3. img.dataset.src를 통해 data-src에 접근하여 `src prop에 다시 넣어준다.

이렇게 되면 사용자에게 보이는 시점에 src prop에 이미지의 위치가 할당되어 이미지가 로딩된다.

참고

이때 각 img 태그가 차지하고 있는 위치가 없으므로 사용자의 눈에 매우 이상하게 보일 수 있다. (예를 들어 img 태그안에 이미지가 뿅하고 들어오면 뒤의 컨텐츠들이 뒤로 밀린다.) 따라서 높이와 넓이를 적절하게 설정해주는 것이 중요하다.


css background 속성을 활용한 예시

document.addEventListener("DOMContentLoaded", function() {
  var lazyloadImages;    

  if ("IntersectionObserver" in window) {
    lazyloadImages = document.querySelectorAll(".lazy");
    var imageObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          var image = entry.target;
          image.classList.remove("lazy");
          imageObserver.unobserve(image);
        }
      });
    });

    lazyloadImages.forEach(function(image) {
      imageObserver.observe(image);
    });
  } else {  
    var lazyloadThrottleTimeout;
    lazyloadImages = document.querySelectorAll(".lazy");
    
    function lazyload () {
      if(lazyloadThrottleTimeout) {
        clearTimeout(lazyloadThrottleTimeout);
      }    

      lazyloadThrottleTimeout = setTimeout(function() {
        var scrollTop = window.pageYOffset;
        lazyloadImages.forEach(function(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-oberser API

#bg-image.lazy {
   background-image: none;
   background-color: #F1F1FA;
}
#bg-image {
  background-image: url("https://ik.imagekit.io/demo/img/image10.jpeg?tr=w-600,h-400");
  max-width: 600px;
  height: 400px;
}

lazy class가 빠지면 사진이 적용된다.


장점

1) 사용자가 처음 웹사이트에 접근할 때 리소스의 일부만 다운로드 되기 때문에 콘텐츠의 제공 속도가 빠르다. 즉 성능이 향상된다.
2) 콘텐츠가 지속적으로 공급되기 때문에 사용자가 웹사이트를 이탈할 확률을 낮출 수 있다.
3) 사용자가 필요로 하는 경우에만 콘텐츠를 불러오기 때문에 배터리, 시간, 시스템 부담 등이 낮아진다. 즉 비용이 감소된다.

참고:
조사한 바로는 대부분의 상황에서 lazy loading을 구현하는 것이 좋아보인다.
굳이 단점을 뽑자면, 이미지가 너~무 클 때는 원하는 타이밍에 로드되지 않을 수 있다는 점이다. 또한, 이 이미지를 로드할 때 브라우저의 메인 스레드를 차지하는데 이때문에 뚝뚝 끊기는 현상을 일으킬 수 있다고 한다.

LazyLoading 구현을 확인하는 법

개발하면서 lazyloading의 구현이 잘 되어있는지 확인하고 싶다면
크롬 개발자 도구 -> 네트워크 -> img를 들어가서 페이지를 직접 스크롤 할 때, 어떤 이미지들이 다운로드 받아지는지 보면 된다.

0개의 댓글