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.type
에background
라면v-lazy-load:background
디렉티브를 사용하고, props.type
이src
라면v-lazy-load
디렉티브를 사용한다.- 그리고 재사용되는 스타일은
<style />
스크립트에 선언한다.
- 그러면, 컴포넌트 방식으로 지연로드를 구현할 수 있다.
// 배경 이미지 지연 로드
<LazyLoad :src="src" :alt="Background Image" type="background">
Background
</LazyLoad>
// 이미지 지연 로드
<LazyLoad :src="src" :alt="Background Image">
Background
</LazyLoad>
- 실제 동작은 아래 playground에서 볼 수 있다. (전체 코드 보기)
3. 마치며…
이번 시간에는 “이미지, 배경이미지의 지연 로드 구현 방법” 시리즈의 2번째인 Vue로 구현하는 방법을 알아보았다.
여러 기능을 제공하는 라이브러리를 사용해도 좋지만, 많은 기능이 필요치 않다면 실제로 구현하는 게 좋다.
만약 해당 포스팅처럼, 커스텀 디렉티브 + 커스텀 컴포넌트 조합으로 구현하고자 한다면 아래 링크 코드를 참고하면 된다 🙂
src/main.ts
: 커스텀 디렉티브를 등록한 코드 (코드보기)
directives/lazyload.ts
: 커스텀 디렉티브 (코드보기)
components/LazyLoad.vue
: 재사용 컴포넌트 (코드보기)
반응형
'개발 기술 > 개발 이야기' 카테고리의 다른 글
아이콘 컴포넌트 렌더링 방식, 정말 좋을까? (with. 빌드 시간, FID, TBT 등 비교) (0) | 2024.08.11 |
---|---|
[JS/CSS] corner smoothing을 구현하는 법(feat. 부드러운 둥근 모서리) (0) | 2024.06.18 |
이미지, 배경이미지의 지연 로드 구현 방법 (with. intersectionObserver API) (0) | 2024.05.30 |
[React] 아이콘 컴포넌트를 선언하는 3가지 방법 (0) | 2024.04.14 |
[vscode] 컬러 변수 뷰어 만들기(2) - colorvariabletracker (0) | 2024.03.31 |
댓글