[JavaScript]이미지 최적화

Main·2023년 6월 11일
4

JavaScript

목록 보기
11/11
post-thumbnail

이미지 최적화가 필요한 이유

(1) 웹사이트 로딩 속도 개선

고해상도 이미지나 최적화되지 않은 이미지는 페이지 로딩 속도를 느리게 만듭니다. 로딩 속도가 느려지면 사용자 경험이 저하되고, 방문자가 페이지를 이탈할 가능성이 커집니다.

(2) SEO(검색 엔진 최적화) 향상

구글과 같은 검색 엔진은 페이지 속도를 중요한 순위 요소로 고려합니다. 이미지 최적화를 통해 웹사이트가 더 빠르게 로드되면 검색 엔진 순위가 올라가고 더 많은 방문자를 유치할 수 있습니다.

(3) 트래픽 및 대역폭 절약

이미지 파일 크기가 크면 서버의 대역폭을 많이 차지하게 됩니다. 특히 모바일 사용자가 많은 경우, 최적화되지 않은 이미지는 불필요한 데이터 소모를 유발할 수 있습니다.

(4) 사용자 경험(UX) 향상

빠른 로딩 속도와 적절한 해상도의 이미지는 사용자 경험을 향상시키고, 사이트의 신뢰도를 높이며, 궁극적으로 전환율(구매, 회원가입 등)을 증가시키는 데 도움을 줍니다.

(5) 서버 저장 공간 절약

서버 저장 공간을 효율적으로 사용하려면 불필요하게 큰 이미지를 줄이고 최적화하는 것이 중요합니다.


이미지 최적화의 여러가지 방법

(1) 필요한 사이즈로 이미지 크기를 조절

웹 사이트에서 사용하는 이미지의 크기는 화면 해상도와 디자인에 맞게 최적화해야 합니다. 불필요하게 큰 이미지는 로딩 속도를 느리게 하고, 작은 이미지는 품질이 저하될 수 있으므로 적절한 크기로 조정하는 것이 중요합니다.

✅ 이미지 크기 조절 시 고려해야 할 요소

디스플레이 해상도 고려

  • 일반적인 웹 페이지는 72~96dpi(Dots Per Inch)의 해상도를 사용합니다.
    고해상도(레티나) 디스플레이에서는 선명한 이미지를 위해 2배 크기의 이미지를 준비하는 것이 좋습니다. 예를 들어, 100px × 100px 크기의 이미지를 레티나 디스플레이에서도 선명하게 보이게 하려면 200px × 200px 크기의 이미지를 제공하는 것이 좋습니다.

    💡 레티나 디스플레이(Retina Display)란 ?
    레티나 디스플레이(Retina Display)는 애플(Apple)에서 개발한 고해상도 디스플레이 기술의 마케팅 용어입니다. 기본적으로 사람의 눈으로 개별 픽셀을 식별할 수 없을 정도로 높은 픽셀 밀도를 가진 디스플레이를 의미합니다.

컨테이너 크기에 맞는 이미지 사용

  • CSS를 사용해 크기를 줄이기보다는 처음부터 적절한 크기의 이미지를 생성하여 불필요한 리소스 낭비를 방지해야 합니다.
  • 예를 들어, 웹사이트 콘텐츠 영역이 1200px이라면 2000px 크기의 이미지는 필요하지 않습니다.
  • 필요 이상으로 큰 이미지를 사용하면 브라우저가 리소스를 불필요하게 많이 사용하여 페이지 로딩 속도가 느려집니다.

반응형 웹 디자인(Responsive Design) 적용

  • 모바일, 태블릿, 데스크톱 등 다양한 화면 크기에 맞는 이미지 제공이 필요합니다.
  • srcsetsizes 속성을 활용하면 브라우저가 자동으로 적절한 크기의 이미지를 선택하여 로드할 수 있습니다.
<img src="image-600.jpg" 
     srcset="image-600.jpg 600w, image-1200.jpg 1200w" 
     sizes="(max-width: 600px) 600px, 1200px" 
     alt="최적화된 이미지">

src 속성은 기본적으로 표시될 이미지 파일을 지정합니다.
srcset을 지원하지 않는 브라우저에서는 이 이미지가 사용됩니다.
srcset 속성은 여러 개의 이미지 파일을 해상도(너비)와 함께 정의합니다. 이미지 파일명과 width 또는 density 설명을 쉼표로 구분합니다.
브라우저는 현재 뷰포트 크기 및 디바이스 픽셀 비율(DPR)을 고려하여 최적의 이미지를 선택합니다.

sizes 속성은 뷰포트 크기에 따라 사용할 이미지의 논리적 크기를 지정합니다.
(max-width: 600px) 600px → 화면 너비가 600px 이하라면, 600px 크기의 이미지를 사용합니다.
1200px → 기본적으로 1200px 크기의 이미지를 사용합니다.
이를 통해 브라우저가 srcset 내에서 가장 적절한 이미지를 선택할 수 있도록 유도합니다.


✅ 이미지 사이즈와 Reflow

🔹 Reflow란?
Reflow(리플로우)는 웹 페이지에서 요소의 크기나 위치가 변경될 때, 브라우저가 다시 레이아웃을 계산하는 과정입니다.

이미지를 로드할 때 크기가 명시되지 않으면 브라우저는 먼저 HTML과 CSS를 해석하고, 이미지가 로드된 후에야 정확한 크기를 계산하여 배치합니다.
이 과정에서 다른 요소들의 위치가 변경될 수 있으며, 이는 불필요한 Reflow 발생을 유발하여 성능 저하를 초래할 수 있습니다.

📌 Reflow가 발생하는 경우

  • 이미지 크기가 명시되지 않아 로딩 후 레이아웃이 변경될 때
  • CSS 스타일 변경으로 인해 요소의 크기나 위치가 달라질 때
  • DOM 요소가 추가/삭제되거나, 브라우저 창 크기가 변경될 때

🔹 이미지 Reflow를 줄이는 방법

① img 태그에 width, height 속성을 명시적으로 선언하기

이미지의 크기를 미리 설정하면 브라우저가 배치를 미리 계산할 수 있어 Reflow를 줄일 수 있습니다.

<img src="img1.jpg" width="640" height="360" alt="img1">

위처럼 크기를 지정하면, 브라우저는 이미지가 로드되지 않은 상태에서도 공간을 예약하고 이후의 레이아웃 변경을 최소화할 수 있습니다.

② CSS에서 max-width, aspect-ratio 함께 사용하기

img {
  max-width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
}

반응형 디자인을 위해 max-width: 100%를 사용하면 이미지 크기가 자동 조정되지만, 크기를 명시적으로 지정하지 않으면 초기 레이아웃을 계산할 때 공간을 예약하지 않아 Reflow가 발생할 수 있습니다. 이를 방지하기 위해 aspect-ratio를 함께 사용해야합니다.

③ 레이지 로딩(Lazy Loading) 사용
한 번에 모든 이미지를 로드하는 대신, 화면에 보이는 이미지부터 로드하면 리플로우를 줄일 수 있습니다. 이를 위해 HTML의 loading="lazy" 속성을 활용하거나 자바스크립트 Intersection Observer API를 활용할 수 있습니다. 이 내용은 (3) 레이지 로딩(Lazy Loading)에서 자세히 다룹니다.


(2) 차세대 이미지 포맷 사용

차세대 이미지 포맷 종류

  • WEBP : 이미지의 더 나은 무손실 및 손실 압축을 위해 개발된 이미지 포맷으로 JPEG 및 PNG에 보다 동일한 품질에 대해 35% 더 작은 이미지 용량을 가집니다. 하지만 모든 브라우저에서 지원하지 않습니다.

  • AVIF : 손실 압축과 비 손실 압축을 전부 지원하기 때문에 WebP처럼 GIF, PNG, JPEG 등의 상용 이미지 포맷을 대체할 수 있습니다. 또한 애니메이션 기능이 있으며, 압축 효율이 뛰어나다는 점에서 WEBP와 비슷합니다.

차세대 이미지 형식 WebP와 AVIF는 기존 이미지 포맷(JPEG, PNG)보다 압축 효율이 뛰어나 이미지 용량을 줄이고 웹사이트 로딩 속도를 개선할 수 있습니다. 하지만 모든 브라우저에서 이러한 형식을 지원하지 않기 때문에, 브라우저 호환성을 보장하려면 점진적 향상 기법을 적용해야 합니다.

💡 점진적 향상 기법 ?
점진적 향상법은 기본적으로 구식 기술 환경에서 동작할 수 있는 기능을 구현하고, 최신 기술을 사용할 수 있는 환경에서는 더 나은 사용자 경험을 제공할 수 있는 최신 기술을 제공하는 방법

차세대 이미지를 점진적 향상 기법 방식을 html 태그인 picture, source 태그를 이용한 방식과 javascript를 이용한 방식에 대해 알아보겠습니다.

picture, source 태그를 이용하여 차세대 이미지 포맷 사용하기

picture 태그와 source 태그를 사용하면, 간단하고 편리하게 브라우저가 지원하는 이미지 형식에 따라 적절한 이미지를 선택하도록 설정할 수 있습니다. 최신 브라우저는 WebP나 AVIF를 우선적으로 로드하고, 지원하지 않을 경우 img 태그에서 제공하는 기본 이미지를 로드합니다.
이 방식은 picture 태그 내에서 제공하는 모든 이미지(차세대 이미지 형식과 차세대 이미지 형식이 지원하지 않는 경우 사용할 이미지 모두)를 브라우저가 요청하는 경우가 있어, 이미지 로드 비용이 늘어날 수 있습니다.

<picture>
  <source srcset="img.avif" type="image/avif" />
  <source srcset="img.webp" type="image/webp" />
  <img src="img.jpeg" alt="this is img" />
</picture>

javascript 이용하여 차세대 이미지 포맷 사용하기

JavaScript를 이용하면 브라우저가 특정 이미지 형식(WebP)을 지원하는지 확인하고, 지원 여부에 따라 동적으로 이미지를 변경할 수 있습니다.
동적으로 이미지를 로드하기 때문에 차세대 이미지, 차세대 이미지가 지원하지 않는 경우 사용할 이미지 중 사용할 이미지만 로드하기 때문에 리소스 낭비를 줄일 수 있습니다.
하지만 초기 로드시 javascript 실행 비용이 추가되며, 구현이 복잡해지고, 디버깅이 어려워질 수 있습니다.

// 현재 브라우저가 webp 이미지를 지원하는지 판단하는 함수
function detectWebpSupport() {
  const image = new Image();
  // 1px x 1px WebP 이미지
  const webpdata = "data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=";
  const callback = (event) => {
   // event.type이 "load"인 경우와 이미지의 너비(image.width)가 1 픽셀인 경우를 검사하여 브라우저가 WebP 이미지를 지원하는지 여부를 판별
  const result = event?.type === "load" && image.width === 1;
    if (result) {
      // webp이미지가 지원된다면 body에 webp class를 추가
      document.body.classList.add("webp");
    }
    else {
      / webp이미지가 지원된다면 body에 no-webp class를 추가
      document.body.classList.add("no-webp");
    }
  };
  image.onerror = callback;
  image.onload = callback;
  image.src = webpdata;
}

// webp 이미지를 지원유무에 따라 다른 이미지 형식을 반환 해주는 함수
// 인자 값으로는 webpSupported: webp이미지 지원 유무, webp이미지 경로, webp 대신 사용할 이미지 형식
 const resolveWebp = (webpSupported,img, fallbackExt) => {
  // 
  const ext = img.split(".").pop();
  if (!webpSupported && ext === "webp") {

    return img.replace("/webp", "").replace(".webp", `.${fallbackExt}`); // 현재 이미지 경로(/images/webp/...)/webp 경로를 제거(/images)하고, 이미지 형식(.webp=>.{fallbackExt})을 수정
  }
  return img;
};

webp 지원 유무를 body의 classList에 webp가 있는지 없는지로 판별하여 동적으로 webp 이미지 적용합니다.


(3) 이미지 스프라이트 기법 사용

여러 개의 배경 이미지를 하나의 파일로 제작한 후 background-position 속성을 이용하여 이미지를 배치하는 방법입니다.
아래는 현재 네이버에서 사용하는 스프라이트 이미지입니다.
네이버
이미지 스프라이트를 이용하면 하나의 이미지를 이용하기 때문에 이미지 용량을 줄일 수 있으며, 하나의 이미지만 받아오면 모든 이미지를 사용할 수 있기 때문에 이미지의 로딩 속도 또한 줄어들게 됩니다. 하지만 이미지 스프라이트의 이미지의 수가 늘어나게 되면, 이미지 용량이 많이 커질 수 있으며, 일부 이미지, 로고의 수정이 필요한 경우 유지 보수가 까다로울 수 있습니다.

이미지 스프라이트 기법 사용 예시

아래 스프라이트 이미지를 이용하여 각 아이콘 이미지를 생성해보겠습니다.

background-position 계산하기
스프라이트 이미지를 적용하기 위해서는 각 아이콘의 위치(좌표)를 계산해야 합니다.

X좌표와 Y좌표는 픽셀 단위로 계산됩니다.
이미지를 오른쪽 또는 아래로 이동하려면 음수 값을 사용합니다.

현재 스프라이트 이미지에서 아래와 같이 좌표를 적용합니다 :
첫 번째 아이콘: (0px, 0px)
두 번째 아이콘: (-37px, 0px)
세 번째 아이콘: (0px, -37px)
네 번째 아이콘: (-37px, -37px)

CSS로 설정하면 다음과 같습니다:

.naver1::after { background-position: 0px 0px; }
.naver2::after { background-position: -37px 0px; }
.naver3::after { background-position: 0px -37px; }
.naver4::after { background-position: -37px -37px; }

background-size 지정
스프라이트 이미지의 전체 크기를 명시하여 브라우저가 올바르게 렌더링할 수 있도록 설정합니다.

background-size: 67px 67px;

적용 코드

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>sprite img</title>
    <style>
      a{
        display: inline-block;
        position: relative;
        padding: 10px 10px 10px 45px;
        border: 1px solid #bdbdbd;
        border-radius: 10px;
        text-decoration: none;
        color: inherit;
      }
      a::after {
        content: "";
        position: absolute;
        top: 50%;
        left: 10px;
        transform: translateY(-50%);
        background: url("img/sprites.png") no-repeat;
        width: 30px;
        height: 30px;
      }
      .naver1::after{
        background-position: 0px 0px;
        background-size: 67px 67px;
      }
      .naver2::after{
        background-position: -37px 0px;
        background-size: 67px 67px;
      }
      .naver3::after{
        background-position: 0px -37px;
        background-size: 67px 67px;
      }
      .naver4::after{
        background-position: -37px -37px;
        background-size: 67px 67px;
      }
    </style>
  </head>
  <body>
    <a href="#" class="naver1">네이버 1</a>
    <a href="#" class="naver2">네이버 2</a>
    <a href="#" class="naver3">네이버 3</a>
    <a href="#" class="naver4">네이버 4</a>
  </body>
</html>

(3) 레이지 로딩(Lazy Loading)

Image Lazy Loading은 페이지 안에 있는 실제 이미지들이 실제로 화면에 보여질 필요가 있을 때 로딩을 할 수 있도록 하는 기술로 페이지 내에서 바로 로딩을 하지 않고 로딩 시점을 뒤로 미루는 것으로 페이지 초기 로딩 시 필요로 한 이미지의 수를 줄일 수 있으며, 또한 웹 성능과 디바이스 내 리소스 활용도 증가, 그리고 연관된 비용을 줄이는데 도움을 줄 수 있습니다.


Lazy-loading 사용방법

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

<picture>
  <source media="(min-width: 800px)" srcset="large.jpg 1x, larger.jpg 2x">
  <img src="photo.jpg" loading="lazy">
</picture>

loading 속성은 브라우저의 이미지 로드 시점 여부를 결정 할 수 있다. loading 속성의 값으로 lazy 를 사용하면 브라우저 스크롤에 반응해서 뷰포트 영역에 근접할 때 이미지를 로딩합니다.


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

웹 페이지를 스크롤 할 때 어떤 요소 이미지가 해당 뷰포트내에 교차 되었는지를 판단할 수 있는 방법을 제공합니다. 이 방법을 이용해 해당되는 이미지가 교차 되면 이미지를 로딩할 수 있도록 JavaScript 핸들링합니다.

이미지 지연 로딩 구현은 아래와 같은 순서로 동작합니다 :
1. Intersection Observer의 options 객체 생성

  • root에 타겟 요소의 가시성을 확인할 뷰포트 요소
  • rootMargin: root의 각 측면의 영역을 수축 또는 증가
  • threshold: observer의 callback이 실행될 타겟 요소의 가시성 퍼센트
  1. Intersection Observer의 callback 함수 생성
    • 타겟 요소와 root가 교차된 상태인지의 여부 확인
    • 교차된 타겟 요소의 dataset에 등록된 이미지 주소를 src에 할당하여 이미지 로딩
    • 이미지 로딩이 완료된 타겟 요소는 관측 요소에서 제외
  2. IntersectionObserver(callback, options) 인스턴스 생성
  3. IntersectionObserver 인스턴스에 타겟 요소들을 등록

Intersection Observer API 사용예시

// IntersectionObserver 를 등록한다.
// entries는 위에 data-src로 설정된 img 태그들의 배열 
const observer  = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    // 관찰 대상이 viewport 안에 들어온 경우 image 로드
    if (entry.isIntersecting) {
      // data-src 정보를 타켓의 src 속성에 설정
      entry.target.src = entry.target.dataset.src;
      // 이미지를 불러왔다면 타켓 엘리먼트에 대한 관찰을 멈춘다.
      observer.unobserve(entry.target);
    }
  })
}, options)

// 관찰할 대상을 선언하고, 해당 속성을 관찰시킨다.
const images = document.querySelectorAll('.image');
images.forEach((el) => {
  io.observe(el);
})

Intersection Observer API를 이용한 이미지 lazy-loading 예시 코드

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>img lazy-loading</title>
    <style>
      img {
        background: #d5d9da;
        width: 500px;
        height: 500px;
        display: block;
        margin: 10px auto;
        border: 0;
      }
    </style>
  </head>
  <body>
    <img
      class="lazy-loading"
      data-src="https://cdn.pixabay.com/photo/2016/12/11/12/02/mountains-1899264_1280.jpg"
    />
    <img
      class="lazy-loading"
      data-src="https://cdn.pixabay.com/photo/2017/04/09/09/56/avenue-2215317_1280.jpg"
    />
    <img
      class="lazy-loading"
      data-src="https://cdn.pixabay.com/photo/2016/09/19/07/01/lake-1679708_1280.jpg"
    />
    <img
      class="lazy-loading"
      data-src="https://cdn.pixabay.com/photo/2017/12/29/18/47/mountains-3048299_1280.jpg"
    />
    <img
      class="lazy-loading"
      data-src="https://cdn.pixabay.com/photo/2013/06/12/22/20/mountains-139012_1280.jpg"
    />
    <img
      class="lazy-loading"
      data-src="https://cdn.pixabay.com/photo/2012/10/30/15/55/valley-63564_1280.jpg"
    />
    <script>
      // lazy-loading 이미지 요소 
      const imgs = document.querySelectorAll(".lazy-loading");

      const observerCallback = (entries, observer) => {
        entries.forEach(({ isIntersecting, intersectionRatio, target }) => {
          if (isIntersecting && intersectionRatio > 0) {
             // data-src 정보를 타켓의 src 속성에 설정
            target.src = target.dataset.src;
            // 이미지를 불러왔다면 타켓 엘리먼트에 대한 관찰을 멈춘다.
            target.classList.remove("lazy-loading");
            observer.unobserve(target);
          }
        });
      };

      // 관찰할 대상을 선언하고, 해당 속성을 관찰시킨다.
      const io = new IntersectionObserver(observerCallback);
      imgs.forEach((img) => io.observe(img));
    </script>
  </body>
</html>

적용 결과

이미지가 화면에 보일 때 이미지가 로딩 되는 것을 볼 수 있습니다.


(4) 이미지 압축

사용자가 이미지를 서버로 업로드 할 때 이미지 파일이 실제 사용하는 크기 보다 크다면 리소스 낭비로 이어지게됩니다.이런 리소스 낭비를 방지하기 위해 이미지 업로드전 이미지 압축 과정이 필요합니다. browser-image-compression 라이브러리를 사용하기 쉽게 구현 가능합니다.

이미지 압축은 아래 코드와 같이 사용할 수 있습니다 :

import imageCompression from "browser-image-compression"

const imgCompression = async (file) => {
  try{
    const options = {
        maxSizeMB: 10, // 이미지 최대 용량
        maxWidthOrHeight: 256, // 이미지 최대 너비 및 높이
        useWebWorker: true, // webworker 적용 유무
        // webworker : 웹 워커 API가 멀티 스레딩을 지원하게 되어 워커를 이용하면 워커에서 작성된 스크립트는 
        // 메인 스레드에서 분기되어 독립된 스레드로 실행되기 때문에 메모리 자원을 효율적으로 사용할 수 있다.
    }
    // 압축된 이미지 blob
    const compressedFileBlob = await imageCompression(file, options);
    // blob 형식의 이미지를 file 형식으로 변환
    const compressedFile = new File([compressedFileBlob], file.name, { type: file.type });
    // 미리보기 이미지를 URL 생성
    const preview = await imageCompression.getDataUrlFromFile(file);
    // 압축된 파일과 미리보기 이미지를 반환
    return {compressedFile, preview}
  } catch(error) {
    console.log(error)
  }
}

(5) UX향상을 위한 점진적 이미지 로딩 기법

점진적 로딩 기법은 실제 이미지가 로드 되기 전까지 미리 보기(대체 이미지, 저화질의 이미지) 이미지를 표시하는 기법입니다.

이미지가 로딩되는 동안에는 이미지가 나타나지 않는 현상으로 사용자 경험에 좋지 못한 영향을 줍니다. 특히 이미지 용량이 클 경우 로딩 시간이 길어지기 때문에 이런 현상이 더 뚜렷하게 나타나게됩니다. 점진적 로딩 기법을 활용하면, 이를 해결하고 사용자 경험을 개선할 수 있습니다.

점진적 이미지 로딩 기법 예시 코드

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      #progressiveImg {
        max-width: 500px;
        height: 300px;
        margin: 0 auto;
        display: block;
      }
    </style>
  </head>
  <body>
    <img id="progressiveImg" alt="lake" />

    <script>
      const img = document.querySelector("#progressiveImg");
      const ProgressiveImg = (targetImg, src, placeholderSrc, errorImgSrc) => {
        targetImg.src = placeholderSrc;
        const img = new Image();
        img.src = src;
        img.onload = () => {
          targetImg.src = src;
        };
        img.onerror = () => {
          targetImg.src = errorImgSrc;
        };
      }
      
      ProgressiveImg(
        img,
        "https://cdn.pixabay.com/photo/2023/06/17/20/15/lake-8070741_1280.png",
        "https://koreandummyjson.site/api/image/500x300",
        "https://koreandummyjson.site/api/image/500x300"
      );
    </script>
  </body>
</html>
  • ProgressiveImg 함수는 인자 값으로 targetImg(대상 이미지 요소), src(대상 이미지 경로), placeholderSrc(대체 이미지 경로), errorImgSrc(에러 이미지 경로)를 받습니다.
  • 이미지 요소를 생성해서 해당 이미지 요소에 인자로 받은 src를 넣어 로딩 유무를 파악합니다.
  • 이미지가 로딩된다면 실제 이미지의 src에 로딩된 이미지 src가 들어가게됩니다.
  • 만약 에러가 발생 한다면 실제 이미지의 src에 에러로 넣을 이미지 src가 들어가게됩니다.

적용 결과 (빠른 3g환경 캐시 사용 중지 후 촬영한 결과)

  • 점진적 이미지 로딩 적용 전
    대체 이미지 없이 이미지가 로딩되는 것을 볼 수 있습니다.
  • 점진적 이미지 로딩 적용 후
    이미지 로딩전 대체 이미지가 나오고, 이미지가 모두 로딩되면 실제 이미지가 나오는 것을 볼 수 있습니다.

💡 실습에 사용할 더미 이미지가 필요하다면 ?
koreandummyjson.site에서 다양한 해상도의 더미 이미지 데이터를 받아 실습해보세요. 이외 다양한 한국어 더미 데이터를 지원합니다.
프로젝트 내용은 [project] korean-dummy-json 글을 참고해주세요.


정리

이미지 최적화 방법에는 여러가지가 있지만 대표적인 위 다섯 가지 방법들에 대해 알아보았습니다. 위 방법들을 통해 실제로 많은 리소스를 줄일 수 있으며, 사용자 경험역시 향상 시킬 수 있습니다.


참고 사이트

profile
함께 개선하는 프론트엔드 개발자

0개의 댓글