개발 기술/개발 이야기

이미지, 배경이미지의 지연 로드 구현 방법(with. Vue)

by GicoMomg 2024. 6. 10.

1. 들어가기 전에…

  • 이미지 지연 로드(LazyLoad)는 웹 페이지가 로드될 때 모든 이미지를 다운로드하지 않고,
  • 사용자가 이미지를 필요로 할 때(예: 스크롤하여 이미지가 보이는 영역에 도달했을 때) 이미지를 로드하는 기술이다.
  • 이 기술을 사용하면, 초기 페이지 로딩 시간을 줄이고 네트워크 트래픽을 최적화해 사용자 경험을 개선할 수 있다.
  • 이번 시간에는 “이미지, 배경이미지의 지연 로드 구현 방법” 시리즈의 두 번째로,
  • Vue의 커스텀 디렉티브로 지연로드를 구현하는 방법을 알아보았다.

시리즈 순서
(1) 이미지, 배경이미지의 지연 로드 구현 방법 (with. intersectionObserver API)
👉 (2-1) 이미지, 배경이미지의 지연 로드 구현 방법 (with. Vue)
(2-2) 이미지, 배경이미지의 지연 로드 구현 방법 (with. React)



2. 커스텀 디렉티브로 LazyLoad 구현하기

  • 커스텀 디렉티브는 요소가 마운트되거나 업데이트될 때 실행할 동작을 정의할 수 있다.
  • 대표적으로 v-if, v-show 등이 있으며, 요소에 재사용되는 로직을 분리해서 관리할 수 있는 장점이 있다.
    (커스텀 디렉티브에 대해 더 알고 싶다면, 공식문서를 참고하는 걸 권장한다.)
  • 이번 시간에는 지연 로드 기능(LazyLoad)을 커스텀 디렉티브와 재사용 가능한 컴포넌트의 조합으로 구현했다. 구현 방법은 다음과 같다.
1. 커스텀 디렉티브에 지연 로드의 핵심 로직을 선언.
2. 재사용 가능한 컴포넌트인 `LazyLoad`에서 커스텀 디렉티브를 사용하고 기본 스타일을 선언.

  • 그러면 재사용 컴포넌트 LazyLoad를 호출해 지연로드 기능을 사용할 수 있다.
// 이미지에 대한 LazyLoad

<LazyLoad :src="src" alt="Image" />
// type이 background라면, 배경이미지에 대한 LazyLoad

<LazyLoad :src="src" alt="Background Image" type="background" >
  Background {{ index + 1 }}
</LazyLoad>


|- src
|-- directives/lazyload.ts  // 커스텀 디렉티브 
|-- components/LazyLoad.vue // 재사용 컴포넌트



1) 커스텀 디렉티브 만들기

(1) 전체 코드 보기

  • lazyLoad.ts은 이미지, 배경이미지의 지연 로딩 기능을 구현한 커스텀 디렉티브이다. (코드보기)
  • 전체 코드는 아래와 같다. 이 중 핵심 코드에 대해 라인별로 설명하겠다.
// src/directives/lazyLoad.ts

import { DirectiveBinding } from "vue";

const lazyLoad = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    const options = {
      root: null,
      rootMargin: "0px",
      threshold: 0.1,
    };

    const callback = (
      entries: IntersectionObserverEntry[],
      observer: IntersectionObserver,
    ) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          el.classList.add(binding.value.loadedClass);
          if (binding.arg === "background") {
            el.style.backgroundImage = `url(${binding.value.src})`;
          } else {
            el.setAttribute("src", binding.value.src);
          }
          observer.unobserve(el);
        }
      });
    };
    const observer = new IntersectionObserver(callback, options);
    observer.observe(el);
  },
};

export default lazyLoad;

(2) 요소가 화면에 그려질 때, 옵저버 등록 및 해제하기

// src/directives/lazyLoad.ts

const lazyLoad = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    // 옵저버 옵션 및 콜백을 정의
  },
};
  • mounted 훅은 디렉티브가 요소에 바인딩되고, 요소가 Dom에 삽입될 때 호출되는 훅이다.
  • 간단히 생각해서, 요소가 처음 화면에 그려질 때 실행되는 훅이라고 보면 된다.
  • 이 훅 단계에서 이미지 지연 로딩을 위한 옵저버 등록, 해제를 진행한다.

(3) Intersection Observer에 등록한 지연 로드 콜백 정의하기

// src/directives/lazyLoad.ts

import { DirectiveBinding } from "vue";

const lazyLoad = {
    ...
    // (a)
    const callback = (
      entries: IntersectionObserverEntry[],
      observer: IntersectionObserver,
    ) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) { // (b)
          el.classList.add(binding.value.loadedClass);

            // (c)
          if (binding.arg === "background") { 
            el.style.backgroundImage = `url(${binding.value.src})`;
          } else {
              // (d)
              el.setAttribute("src", binding.value.src); 
          }
          // (e)
          observer.unobserve(el);
        }
      });
    };
    const observer = new IntersectionObserver(callback, options);
    observer.observe(el);
  },
};

export default lazyLoad;
  • (a) callback()는 요소(el)가 화면에 나타날 때, 실행할 함수를 정의한다.
  • (b) 만약 요소가 화면에 나타났을 때,
  • (c) 디렉티브 인수가 background라면 요소의 backgroundImage에 이미지를 등록하고,
  • (d) 그렇지 않으면 요소의 src 속성에 이미지를 등록한다.
  • (e) 마지막으로, 이미지가 로드되면 관찰을 중지해 재 관찰이 발생하는 경우를 방지한다.

(4) 디렉티브 등록하기

  • src/main.ts에 커스텀 디렉티브로 등록한다. (코드보기)
// src/main.ts

import { createApp } from "vue";
import "./style.css";
import App from "./App.vue";
import lazyLoad from "./directives/lazyLoad";  // 추가!

createApp(App).directive("lazy-load", lazyLoad).mount("#app");

  • 그러면 아래와 같은 형태로, 지연 로드 디렉티브를 쓸 수 있게 된다.
// 이미지의 지연로드
<img v-lazy-load="{ src: 'image-url.jpg', loadedClass: 'loaded' }" />

// 배경 이미지의 지연로드
<div v-lazy-load:background="{ src: 'background-image-url.jpg', loadedClass: 'loaded' }"></div>  



2) 재사용 컴포넌트 만들기

😢 하지만, 지연로드 대상에 매번 스타일을 줘야 하는 번거로움이 있다.
이 경우, 커스텀 디렉티브를 사용하는 재사용 컴포넌트를 구현하면 된다. (코드보기)

// src/components/LazyLoad.vue

<template>
  <div
    v-if="props.type === 'background'"
    v-lazy-load:background="{
      src: props.src,
      loadedClass: 'loaded',
    }"
    class="lazy-background"
  >
    <slot></slot>
  </div>
  <div v-else class="lazy-container">
    <img
      :alt="props.alt"
      v-lazy-load="{
        src: props.src,
        loadedClass: 'loaded',
      }"
      class="lazy"
    />
  </div>
</template>

<script setup lang="ts">
...
</style>
  • 코드는 간단한데, props.typebackground라면 v-lazy-load:background 디렉티브를 사용하고,
  • props.typesrc라면 v-lazy-load 디렉티브를 사용한다.
  • 그리고 재사용되는 스타일은 <style /> 스크립트에 선언한다.

  • 그러면, 컴포넌트 방식으로 지연로드를 구현할 수 있다.
// 배경 이미지 지연 로드
<LazyLoad :src="src" :alt="Background Image" type="background">
  Background
</LazyLoad>

// 이미지 지연 로드
<LazyLoad :src="src" :alt="Background Image">
  Background
</LazyLoad>



3. 마치며…

  • 이번 시간에는 “이미지, 배경이미지의 지연 로드 구현 방법” 시리즈의 2번째인 Vue로 구현하는 방법을 알아보았다.

  • 여러 기능을 제공하는 라이브러리를 사용해도 좋지만, 많은 기능이 필요치 않다면 실제로 구현하는 게 좋다.

  • 만약 해당 포스팅처럼, 커스텀 디렉티브 + 커스텀 컴포넌트 조합으로 구현하고자 한다면 아래 링크 코드를 참고하면 된다 🙂

    src/main.ts: 커스텀 디렉티브를 등록한 코드 (코드보기)
    directives/lazyload.ts: 커스텀 디렉티브 (코드보기)
    components/LazyLoad.vue: 재사용 컴포넌트 (코드보기)



반응형

댓글