Image Lazy Loading 를 이용한 이미지 최적화

서준·2023년 9월 1일
1
post-thumbnail

Image Lazy Loading이란?

Image lazy loading은 아직 화면에 보여지지 않은 이미지들은 로딩 시점을 뒤로 미루어 초기 로딩시간을 단축할 수 있는 웹 성능 최적화 기법.

언제 사용하면 좋을까?

위 이미지는 팔로우한 사용자들이 작성한 게시글들을 화면에 보여주는 홈 피드이다. 팔로우한 사람들이 점점 많아질수록 홈 피드에는 로드해야 될 게시글들 또한 많아져 로딩시간이 길어질 것이므로 최적화를 위해 Image Lazy Loading 을 사용하기로 하였다.

Image Lazy Loading 적용시


이미지가 로딩될 때 원본 이미지 대신 저화질의 이미지를 보여줌으로써 UX를 향상 시켰다.

구현 방법

Image lazy loading 기법은 홈피드에 로드되는 게시글 이미지 뿐만 아니라 클래스 이미지나, 유저 게시글 이미지에도 중복해서 사용할 수 있기 때문에 재사용성을 고려해
ProgressiveImg 컴포넌트를 만들고, 각 컴포넌트 내의 이미지에 적용시켜 주도록 설계하였다.

웹페이지의 특정 요소가 화면에 보이는지 아닌지를 감지하는 API인 react-intersection-observer 라이브러리를 이용하여 lazy-loading를 구현하였다.

전체 코드

//ProgressiveImg.jsx
import React, { useEffect, useState } from 'react';
import { Img } from './ProgressiveImgStyle';

import { useInView } from 'react-intersection-observer';

import NoImg from '../../../assets/img/No_Image_Available.jpg'; //원본 이미지 로드 실패시 보여줄 이미지 
export default function ProgressiveImg({
  placeholderSrc,
  src,
  styles,
  ...props
}) {
  const [imgSrc, setImgSrc] = useState(placeholderSrc || src);
  const [isLazy, setIsLazy] = useState(true);
  const { ref, inView } = useInView();
  const customClass = isLazy ? 'loading' : 'loaded';
  useEffect(() => {
    if (inView && imgSrc === placeholderSrc) {
      const img = new Image();
      img.src = src;
      img.onload = () => {
        setImgSrc(src);
        setIsLazy(false);
      };
      img.onerror = () => {
        setImgSrc(NoImg);
        setIsLazy(false);
      };
    }
  }, [src, inView]);

  return (
    <Img
      {...{ src: imgSrc, ...props }}
      className={customClass}
      style={styles}
      loading='lazy'
      alt={props.alt || ''}
      ref={ref}
    />
  );
}

주요 코드

import { useInView } from 'react-intersection-observer';

const { ref, inView } = useInView();

react-intersection-observeruse 라이브러리에서 제공하는 InView hook을 사용하여 이미지 요소가 화면에 보이는지 여부를 감지한다. 화면에 보이는 경우, inView는 true가 된다.

  • ProgressvieImg Props

    • placeholderSrc : 원본 이미지가 로딩되기전 보여줄 저화질의 이미지 url
    • src : 실제 로드할 원본 이미지의 URL
    • ...props : 기타 이미지들에 전달할 props
  • 상태 관리

    • imgSrc : 현재 보여줄 이미지의 URL이며 초기값은 placeholderSrc 또는 src로 설정

    • isLazy : 이미지가 lazy-loading 중인지의 상태를 나타냄. 초기값은 true로 설정, 이미지가 로드되면 false로 변경되고 이미지 블러처리를 해제

  • 이미지 로딩 로직
const img = new Image();
img.src = src;

  	img.onload = () => {
        setImgSrc(src);
        setIsLazy(false);
      };

 	 img.onerror = () => {
        console.log('이미지 로드 실패:', src);
        setImgSrc(NoImg); 
        setIsLazy(false);
      };

Image 객체를 생성하여 원본 이미지를 로드합니다. onload 이벤트는 이미지 로드가 성공적으로 완료되었을 때 setImgSrc에 원본 이미지 src를, onerror 이벤트는 이미지 로드에 실패했을 보여줄 이미지 src를 설정한다.

//ProgressiveImgStyle.jsx
import styled from 'styled-components';

export const Img = styled.img`
  transition: all 0.5s;

  &.loading {
    filter: blur(10px);
    clip-path: inset(0);
  }
  &.loaded {
    filter: blur(0px);
  }
`;
export const Placeholder = styled.div`
  width: 100%;
  height: 100%;
  background-color: ${(props) => props.color || '#eee'};
`;

저화질 이미지 블러 설정/해제는 ProgressiveImgStyle에서 작성했다.

컴포넌트 적용

이제 위에서 만든 ProgressiveImg 컴포넌트를 작성 게시물 컴포넌트인 PostContent에 적용해보자

//PostContent에 필요한 Import 
import ProgressiveImg from '../ProgressiveImg/ProgressiveImg';
import PlaceholderImg from '../../../assets/img/placeholderImg.svg'; // 로딩 전에 보여줄 저화질 이미지 
  • ProgressiveImg 적용 전
   <img
         key={index}
         src={HandleNormalizeImage(postImage)}
         width={postImages.length > 1 ? '168px' : '304px'}
         alt=''
    /> 
  • ProgressiveImg 적용 후
  <ProgressiveImg
          key={index}
          src={HandleNormalizeImage(postImage)} // // 로딩 되면 보여줄 원본 이미지 
          width={postImages.length > 1 ? '168px' : '304px'}
          alt='게시글 이미지'
          placeholderSrc={PlaceholderImg} // 로딩 전에 보여줄 저화질 이미지 
    />
            

Image Lazy Loading 적용 전 후 비교

개발자 전용 도구 네트워크탭에서 웹 페이지를 로드하는 동안 실제로 네트워크를 통해 전송된 데이터의 양 비교를 해본 결과

이 값들이 크면 페이지 로드 시간이 늘어날 수 있으므로, 최적화 작업에서 이 값을 최소화하는 것이 중요합니다.
이미지 전송량: 9.4MB => 14.7kb ( 99.85% 축소)
최적화 전 로딩속도 : 5.37초 => 3초(2.37초 단축)



페이지 로딩시간이 단축된 것을 확인할 수 있었다.

profile
하나씩 쌓아가는 재미

0개의 댓글