[프론트엔드 최적화] 크롬 개발자 도구 설정, 이미지 Lazy loading

2022. 4. 5. 20:12
반응형

네트워크 탭을 통해 어떤 리소스들이 다운로드 되는지 살펴본다.

이를 위해 임의로 느린 네트워크 환경을 설정해준다.

 


 

1. 크롬 개발자도구(F12) - Network 탭 - SlowDown

 

2. Custom 설정으로 아래와 같이 생성 (preset에 있는 Fast3G, Slow3G, offline은 너무 극단적으로 느리기 때문에 커스텀 옵션을 사용한다)

 

 

 

3. Disable cache 체크

 

 

 


 

세팅을 마친 후 새로고침하면, 아래와 같이 느린 환경에서 어떤 리소스들이 어떤 속도로 로딩되는지 확인할 수 있다.

 

 

히어로 콘텐츠(메인 동영상)는 pending 상태로,

앞의 작은 이미지들이 어느 정도 로딩되고 나서야 매우 느리게 로딩되는 것을 확인할 수 있다.

가장 나중에 로드하게 되면 사용자는 첫 화면에서 아무런 화면을 보지 못하는 경험을 하게 된다 (안 좋은 사용자 경험)

 

이를 해결하기 위해서는

 

1. 작은 이미지들을 빠르게 다운로드할 수 있게 만든다

2. 작은 이미지들이 첫 화면에서 다운로드 되게 하지 말고, 히어로 콘텐츠(메인 동영상)가 먼저 다운로드 되도록 하고, 이미지들은 나중에 처리하도록 한다.

 

1번 방법은 이미지들이 아무리 빨리 다운로드 된다고 하더라도 궁극적인 해결방법이 아니기 때문에,

2번 방법인 'Lazy loading'을 사용하여 문제를 해결하도록 한다. 해당 이미지가 보여지는 순간(=스크롤이 닿을 때) 로드하는 것이다.

 

단점

window.addEventListener('scroll', (e)=> ... ) 방식을 사용해서 판단하는 것인데,

매 스크롤 할 때마다 많은 함수들이 실행되어야 한다.

 

image lazy loading을 사용하려다가 괜히 많은 리소스를 사용할 수 있다는...

 

하지만, 다행히 Intersection Observer를 사용하면 이 문제를 해결할 수 있다.

 

특정 element를 observing하면 이것이 화면에 보여지는지, 안보여지는지 판단할 수 있다.

브라우저 자체적으로 지원하는 것이기 때문에 addEventListener를 사용하는 것보다 성능 면에서 훨씬 유리하다.

 

https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API  

 

Intersection Observer API - Web API | MDN

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

developer.mozilla.org

 

기본적인 사용법

function createObserver() {
  let observer;

  let options = {
    root: null,
    rootMargin: "0px",
    threshold: buildThresholdList()
  };

  observer = new IntersectionObserver(handleIntersect, options);
  
  //boxElement가 화면에 들어오는 순간, handleIntersect라는 함수가 호출된다.
  //boxElement가 화면에서 빠져나가는 순간 위 함수는 한 번 더 호출된다.
  observer.observe(boxElement);
}

 

카드 컴포넌트에 observer 적용

(콘솔로그는 이미지가 화면 안에 들어올 때, 나갈 때 모두 뜬다)

import React, { useEffect, useRef } from "react";

function Card(props) {
  // dom 요소에 직접 접근하기 위해 useRef hooks 사용
  const imgRef = useRef(null);

  //observing을 최초 한 번만 실행
  useEffect(() => {
    const callback = () => {
      console.log("callback");
    };
    const options = {};
    const observer = new IntersectionObserver(callback, options);
    observer.observe(imgRef.current);
  }, []);

  return (
    <div className="Card text-center">
      <img src={props.image} ref={imgRef} />
      <div className="p-5 font-semibold text-gray-700 text-xl md:text-lg lg:text-xl keep-all">
        {props.children}
      </div>
    </div>
  );
}

export default Card;

 

이미지가 화면 안에 들어올 때에만 콘솔로그 메시지가 뜨도록 수정(나갈 때는 X)

import React, { useEffect, useRef } from "react";

function Card(props) {
  // dom 요소에 직접 접근하기 위해 useRef hooks 사용
  const imgRef = useRef(null);

  //observing을 최초 한 번만 실행
  useEffect(() => {
    const options = {};

    const callback = (entries, observer) => {
      entries.forEach((entry) => {
        //화면 안에 이 요소의 값이 들어와있는가, 아닌가를 판단
        if (entry.isIntersecting) {
          console.log("is intersecting");
        }
      });
    };

    const observer = new IntersectionObserver(callback, options);
    observer.observe(imgRef.current);
  }, []);

  return (
    <div className="Card text-center">
      <img src={props.image} ref={imgRef} />
      <div className="p-5 font-semibold text-gray-700 text-xl md:text-lg lg:text-xl keep-all">
        {props.children}
      </div>
    </div>
  );
}

export default Card;

 

 

 

 


Lazy Loading 구현

 

위에서 처리한 console 메시지 대신, 이미지를 로딩하도록 한다.

 

 

1. img 컴포넌트에 data-src 속성을 주고, src 속성은 제거한다.

(그러면 {props.image} 값은 데이터로서 존재하게 되며, 어떠한 img는 이미지 리소스도 로드하지 않는다)

 

2. useEffect안에서 entry(이미지)의 src 값을 data.src 값으로 대체하도록 한다.

 

 

반응형

BELATED ARTICLES

more