1. 들어가기에 앞서
- 각 서비스에는 랜딩 페이지가 존재하며, 랜딩 페이지들은 대개 고화질의 이미지를 사용한다.
- 사용자가 페이지에 처음 방문했을 때, 페이지의 처음부터 끝까지 스크롤할 수 있지만, 일부는 첫 화면만 보고 다른 탭으로 이동하기도 한다.
- 그래서 랜딩 페이지의 모든 고화질 이미지를 한꺼번에 로드한다면, 리소스 낭비가 발생한다.
- 그럼, 어떻게 해야 리소스 낭비를 줄일 수 있을까? 사용자가 접근하지 않은 이미지는 로드하지 않을 수 없을까? 바로 지연 로드 방식을 사용하면 된다!
- 지연 로드는 사용자가 이미지를 볼 때까지 이미지 로드를 미루는 기법이다. 이 기법을 사용하면 초기 로드 시간을 단축하고, 불필요한 리소스 사용을 줄일 수 있다.
- 이번 시리즈에서는 “(1) JavaScript의 IntersectionObserver API로 지연로드를 구현하는 법”, “(2) Vue와 React에서 지연로드를 구현하는 법”을 알아보았다.
시리즈 순서
👉 (1) 이미지, 배경이미지의 지연 로드 구현 방법 (with. intersectionObserver API)
(2) 이미지, 배경이미지의 지연 로드 구현 방법 (with. Vue, React)
2. IntersectionObserver로 지연 로드를 구현해보자!
1) IntersectionObserver란?
- 이미지 지연로드의 핵심은 “유저가 해당 이미지를 봤을 때, 이미지를 로드한다”이다. 여기서 중요한 건 “화면에 요소가 나타났는지 어떻게 감지하는지”이다.
- 스크롤 길이를 비교해서 감지할 수도 있지만, 더 간단한 방법은 바로
IntersectionObserver API
를 사용하는 것이다. IntersectionObserver
는 뷰포트(Viewport)나 특정 부모 요소를 기준으로, 대상 요소가 나타날 때 콜백 함수를 호출하는 API이다.- 이 옵저버는 주로 무한 스크롤 이나 이미지 지연 로드을 구현할 때 쓰인다.
예시 | Observer 사용 전 | Observer 사용 후 |
---|---|---|
무한 스크롤 | 스크롤 위치가 페이지 하단에 도달했는지를 매번 계산하여 확인. 스크롤 이벤트는 매우 빈번하게 발생하므로, Throttling 처리가 필요. |
요소가 뷰포트에 진입할 때만 콜백이 실행되므로, 불필요한 계산을 줄일 수 있음. 스크롤 위치를 수동으로 계산하는 대신, Intersection Observer의 콜백 함수 내에서 필요한 작업만 수행 가능. |
이미지 지연 로드 | 페이지 로드 시점에서 모든 이미지가 즉시 서버에서 로드함. 초기 페이지 로드 시간이 길어짐. 사용자가 실제로 보지 않을 이미지도 로드되므로, 불필요한 네트워크 트래픽이 발생함. |
이미지가 뷰포트에 진입할 때만 실제 이미지를 로드. 페이지 로드 시점에서 이미지를 모두 로드하지 않기 때문에 초기 로드 시간이 단축. 필요한 이미지가 뷰포트에 들어올 때만 로드되어, 사용자에게 더 빠른 반응성을 제공. |
(1) 사용 방법
- 사용 방법은 간단한데, 먼저
new IntersectionObserver
생성자를 사용해 observer 객체를 만든다. - 생성자의 인자로는, 요소가 화면에 나타났을 때 실행할
callback
함수를 넘겨준다.
const observer = new IntersectionObserver(callback);
function callback(entries) {
// entries는 IntersectionObserverEntry 객체의 배열임
if (entries[0].intersectionRatio <= 0) return;
console.log("감시하는 요소가 화면에 나타났다!");
}
- 두 번째 단계로, 감지하고 싶은 요소를
observer
에 등록한다. - 이렇게 하면 해당 요소가 화면에 나타날 때, 미리 등록한
callback
함수가 실행된다.
const targetElement = document.querySelector(".target");
intersectionObserver.observe(targetElement); // 감지 시작!
- 아래 예시는 네 번째 박스가 화면에 나타났을 때 알림창을 띄우는 코드이다.
- 스크롤로 네 번째 박스가 있는 영역에 도달하면, 알림창이 나타난다.
(2) 어떻게 이미지의 lazyload 처리를 할 수 있을까?
😉 html, js로 여러 개의 이미지를 지연 로드하는 방법을 알아보자!
- 먼저, 총 7개의 이미지 태그를 추가한다.
- 각 이미지 태그에는
class="lazy"
와data-src
속성을 추가한다. data-src
에는 이미지의 경로를 지정하고,src
속성은 비워둔다.
<div class="image-container">
<img class="lazy" data-src="https://img1.daum..." alt="Lazy Image 1">
</div>
<div class="image-container">
<img class="lazy" data-src="https://img1.daum..." alt="Lazy Image 2">
</div>
<div class="image-container">
<img class="lazy" data-src="https://img1.daum..." alt="Lazy Image 3">
</div>
<div class="image-container">
<img class="lazy" data-src="https://img1.daum..." alt="Lazy Image 4">
</div>
<div class="image-container">
<img class="lazy" data-src="https://img1.daum..." alt="Lazy Image 5">
</div>
<div class="image-container">
<img class="lazy" data-src="https://img1.daum..." alt="Lazy Image 6">
</div>
<div class="image-container">
<img class="lazy" data-src="https://img1.daum..." alt="Lazy Image 7">
</div>
- 그럼 아래와 같은 형태로 화면이 구성된다. 현재는
src
에 이미지가 지정되지 않아 이미지 박스만 보인다.
😉 이제 intersection Observer를 사용해, 지연 로드을 구현해보자
document.addEventListener('DOMContentLoaded', registerObserver);
- 우선 화면이 처음 렌더링될 때 Intersection Observer를 등록해야한다.
DOMContentLoaded
이벤트는 HTML 문서의 구조가 완전히 로드되고 파싱되었을 때 발생한다.DOMContentLoaded
이벤트가 발생할 때, 옵저버를 등록하는registerObserver
함수를 호출한다.
function registerObserver() {
const options = {
root: null,
rootMargin: '0px',
threshold: 0.5
};
...
}
registerObserver
함수은 옵저버를 등록하고 관리하는 함수이다.options
은 옵저버의 설정값으로,root
,rootMargin
,threshold
을 지정할 수 있다.root
는 관찰의 기준이 되는 요소를 지정하는데, null이면 뷰포트가 기준이 된다.rootMargin
은 관찰 대상의 여백을 지정할 수 있고,threshold
의 경우 0.5로 설정하면 50% 이상 보일 때 콜백을 호출한다.
function registerObserver() {
...
function callback(entries, observer) { // (A), (B)
entries.forEach(entry => {
if (entry.isIntersecting) { // (C)
const img = entry.target;
img.src = img.dataset.src; // (D)
img.classList.add('loaded');// (E)
observer.unobserve(img); // (F)
}
});
};
...
}
callback
함수는 Intersection Observer가 감지한 변화가 있을 때 호출된다.
코드번호 | 설명 |
---|---|
(A) | entries 는 관찰 대상의 상태를 나타내는 객체 배열이다. |
(B) | 현재 Intersection Observer 객체이다. |
(C) | 각 entry.isIntersecting 를 사용해 대상 요소가 화면에 나타났는지 확인한다. |
(D) | 만약 대상 요소가 화면에 나타났다면, 이미지를 로드하는 과정을 거친다. 우선, img.src 에 data-src 속성의 값을 할당하여 실제 이미지를 로드한다. |
(E) | img.classList.add('loaded') 로 loaded 클래스를 추가한다.loaded 클래스가 적용되었을 때, 지연 로드 스타일을 적용할 수 있게 된다. |
(F) | 이미지가 로드된 요소는 더이상 관찰할 필요가 없다.observer.unobserve(img) 를 호출하여 해당 이미지 요소의 관찰을 중지한다. |
function registerObserver() {
...
const observer = new IntersectionObserver(callback, options) // (G)
const lazyImages = document.querySelectorAll('.lazy'); // (H)
lazyImages.forEach(img => observer.observe(img)); // (I)
}
- 마지막으로 앞서 선언한 콜백과 옵션을
intersectionObserver
에 전달한다.
코드번호 | 설명 |
---|---|
(G) | new IntersectionObserver(callback, options) 로 Intersection Observer 객체를 생성하고, callback 함수와 options 설정을 전달한다. |
(H) | 그리고 지연로드 대상이 되는 요소(.lazy )들에 접근한다. |
(I) | observer.observe(img) 를 호출해 지연 로드 대상을 관찰하면 완성이다! |
앞서 설명한 스크립트 전체 코드는 아래와 같다.
document.addEventListener('DOMContentLoaded', registerObserver);
function registerObserver() {
const opiton = {
root: null,
rootMargin: '0px',
threshold: 0.5
};
function callback(entries, observer) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.add('loaded');
observer.unobserve(img);
}
});
};
const observer = new IntersectionObserver(callback, opiton);
const lazyImages = document.querySelectorAll('.lazy');
lazyImages.forEach(img => observer.observe(img));
}
- playground를 보면 이미지가 화면에 나타날 때만 이미지가 로드되는 걸 볼 수 있다 🙂
그럼 실제로 초기 로드 시간, 이미지 다운로드양이 줄어들었을까?
- 크롬의 [네트워크 탭]에서 지연로드 전 후를 비교했을 때, 이미지 리소스 로드시간, 로드양이 감소한 걸 볼 수 있다. (지연로드 전 코드)
(3) 배경이미지의 lazyload 처리는 어떻게 할 수 있을까?
앞서 우리는 이미지를 지연 로드하는 방법을 알아보았다.
그렇다면, 배경 이미지를 지연 로드하는 방법은 없을까? 물론 있다!
- 먼저, 배경 이미지를 설정할 컨테이너(
.background-container
)를 생성한다. data-bg
속성에 실제 배경 이미지 경로를 지정한다.- 여기서 주의할 점은, 초기에는
background-image: none
로 설정해 배경 이미지를 로드하지 않도록 해야한다.
<div class="background-container lazy-background" data-bg="url('https://img1.daum...)">
Background 1
</div>
<div class="background-container lazy-background" data-bg="url('https://img1.daum...)">
Background 2
</div>
<div class="background-container lazy-background" data-bg="url('https://img1.daum...)">
Background 3
</div>
<div class="background-container lazy-background" data-bg="url('https://img1.daum...)">
Background 4
</div>
<div class="background-container lazy-background" data-bg="url('https://img1.daum...)">
Background 5
</div>
- 그럼 아래와 같은 형태로 화면이 구성된다.
background-image: none
이 설정되어 있어background-color
만 보인다.
자! 이제 intersection Observer를 사용해, 배경 이미지 지연 로드을 구현해보자
대략적인 과정은 이미지 지연 로드과 같다.
document.addEventListener('DOMContentLoaded', registerObserver);
DOMContentLoaded
이벤트가 발생할 때, 옵저버를 등록하는registerObserver
함수를 호출한다.
function registerObserver() {
const options = {
root: null, // 관찰 기준이 되는 요소를 지정
rootMargin: '0px', // 관찰 대상의 여백을 지정
threshold: 0.5 // 50% 이상 보일 때 콜백을 호출
};
...
}
registerObserver
함수은 옵저버를 등록 관리하는 함수이다.
function registerObserver() {
...
function callback(entries, observer) { // (A), (B)
entries.forEach(entry => {
if (entry.isIntersecting) { // (C)
const container = entry.target;
container.style.backgroundImage = container.dataset.bg; // (D)
container.classList.add('loaded'); // (E)
observer.unobserve(container); // (F)
}
});
};
...
}
callback
함수는 Intersection Observer가 감지한 변화가 있을 때 호출된다.
코드번호 | 설명 |
---|---|
(A) | entries 는 관찰 대상의 상태를 나타내는 객체 배열이다. |
(B) | 현재 Intersection Observer 객체이다. |
(C) | 각 entry.isIntersecting 를 사용해 대상 요소가 화면에 나타났는지 확인한다. |
(D) | 만약 대상 요소가 화면에 나타났다면, 배경 이미지를 로드하는 과정을 거친다. 먼저, backgroundImage 에 dataset.bg 속성값을 할당하여 배경 이미지를 로드한다. |
(E) | img.classList.add('loaded') 를 적용한다. |
(F) | 배경 이미지가 로드된 요소는 더이상 관찰할 필요가 없다.observer.unobserve(container) 를 호출하여 이미지 컨테이너 요소의 관찰을 중지한다. |
function registerObserver() {
...
const observer = new IntersectionObserver(callback, options);
const lazyBackgrounds = document.querySelectorAll('.lazy-background');
lazyBackgrounds.forEach(container => observer.observe(container));
}
- 이제 마지막으로 콜백과 옵션을
intersectionObserver
에 전달해주자.
코드번호 | 설명 |
---|---|
(G) | new IntersectionObserver(callback, options) 로 Intersection Observer 객체를 생성하고, callback 함수와 options 설정을 전달한다. |
(H) | 그리고 지연로드 대상이 되는 요소(.lazy-container )들에 접근한다. |
(I) | observer.observe(container) 를 호출해 지연 로드 대상을 관찰하면 끝난다. |
앞서 설명한 스크립트 전체 코드는 아래와 같다.
document.addEventListener('DOMContentLoaded', registerObserver);
function registerObserver() {
const options = {
root: null,
rootMargin: '0px',
threshold: 0.5 // 50% 이상 보일 때 콜백 호출
};
function callback(entries, observer) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const container = entry.target;
container.style.backgroundImage = container.dataset.bg;
container.classList.add('loaded');
observer.unobserve(container);
}
});
}
const observer = new IntersectionObserver(callback, options);
const lazyBackgrounds = document.querySelectorAll('.lazy-background');
lazyBackgrounds.forEach(container => observer.observe(container));
}
- playground를 보면 이미지 컨테이너가 화면에 나타날 때만 배경 이미지가 로드되는 걸 볼 수 있다 🙂
3. 마치며…
이번 시간에는 intersectionObserver
의 개념, 사용법 그리고 observer를 사용해 이미지, 배경 이미지의 지연로드를 구현하는 법에 대해 알아보았다.
지연 로드는 웹 페이지의 성능을 최적화하고 사용자 경험을 향상시키는 방법이다.
이 기능을 구현하는 방법 중 하나로 IntersectionObserver
API를 이용할 수 있다. IntersectionObserver
는 요소가 화면에 나타날 때 특정 작업을 수행할 수 있다.
먼저 이미지의 지연 로드는 data-src
속성에 이미지 경로를 저장하고, 요소가 화면에 나타났을 때 src
속성에 data-src
값을 전달해 이미지를 로드했다.
배경 이미지의 지연 로드도 비슷한 방식으로 구현할 수 있었다.
배경 이미지의 경우, 요소의 data-bg
속성에 배경 이미지 경로를 저장하고, 요소가 화면에 나타날 때 이 경로를 backgroundImage
스타일 속성에 할당했다.
이처럼 IntersectionObserver
를 활용하면 이미지와 배경 이미지 모두 지연 로드를 통해 효율적으로 관리할 수 있다.
다음 시간에는 Vue의 커스텀 디렉티브, React의 커스텀 훅으로 지연로드를 구현하는 방법에 대해 알아보겠다.
만약 포스팅 예정인 Vue, React 코드를 먼저 보고 싶다면 아래 링크의 레포에서 볼 수 있다.
'개발 기술 > 개발 이야기' 카테고리의 다른 글
[JS/CSS] corner smoothing을 구현하는 법(feat. 부드러운 둥근 모서리) (0) | 2024.06.18 |
---|---|
이미지, 배경이미지의 지연 로드 구현 방법(with. Vue) (2) | 2024.06.10 |
[React] 아이콘 컴포넌트를 선언하는 3가지 방법 (0) | 2024.04.14 |
[vscode] 컬러 변수 뷰어 만들기(2) - colorvariabletracker (0) | 2024.03.31 |
[vscode] 컬러 변수 뷰어 만들기(1) - webview API 사용법 (4) | 2024.03.16 |
댓글