레이지 로딩이란 웹사이트에서 DOM이 로드되고 나서 컨텐츠를 불러 올때 이미지와 같이 용량이 큰 파일들을 일부러 늦게 로드하는 기법을 말한다. 이미지가 적다면 크게 문제가되진 않겠지만 수십개 또는 수백개라면 레이지 로딩을 하지 않고서는 수백개를 다 불러들어와야만 사용자에게 화면이 보이지만 레이지 로딩 기법을 사용하면 화면에 보일 이미지들만 로드한 뒤 스크롤,리사이즈 등의 이벤트가 발생하면 다른 이미지들을 보여질 부분만 차례대로 로드하는 기법이다.
IE를 제외하고 대부분의 메이저 브라우저에서는 Intersection Observer를 지원한다.
Intersection Observer란 쉽게말해서 어느 요소를 관찰하는 객체이다.
A라는 객체를 만들어서 " 야 너 저기가서 클래스가 '벨로그'인 img 태그를 가진애들 뭐하는지 한번 봐봐 " 라고 시키는것이다.
그리고 우리 ( 개발자들 )은 "A야 보기만 하지말고 아까 걔네들 오는지 안오는지 보다가 집에오면 callback이란 심부름 좀 시켜~"라고 말하는것이다.
코드로 보면
// 지켜 볼 애들을 찾고
const 지켜 볼 애들 = document.querySelectorAll('.벨로그');
// IntersectionObserver를 선언하고
// 관찰만 하지말고 callback이란 일을 시키도록 미리 정의하고
const io = new IntersectionObserver(callback,options);
// 애들을 지켜보기 시작.
(지켜 볼 애들).forEach((한 아이) => {
io.observe(아이);
});
IntersectionObserver( 이하 IO)를 이용하면 요소가 화면에 나올때와 화면에서 사라질때를 알 수 있다. 꼭 화면 ( window)일 필요는 없고
요소를 포함하고 있는 컨테이너 역할을 하는 요소여도 된다.
레이지로딩은 IO를 이용하여 이렇게 화면에 나타나는 순간을 감지하고 이때 이미지를 로드한다.
그렇다면 어떻게 그 순간 이미지를 로드할까?
이미지를 가져 올 경로는 이미지 태그의 src
에 넣는데 우선 이 속성을 비워둔다 그러면 아래와 같이 빈 이미지 형태가 된다.
( img 태그를 사용할때 왠만하면 alt
속성과 width
height
속성을 미리 지정해주자 그러면 브라우저가 해당 요소를 렌더링할때 아래와 같이 이미지의 크기를 미리 정해주기 때문에 이미지가 다 로드 되더라도 다른 요소를 리렌더링 할 필요가 없으므로 렌더링 성능이 향상된다. )
코드로 보자면 아래와 같다.
img src
를 비워두는 대신에 HTML data- 사용자 정의 속성을 이용하여 여기에다가 이미지의 경로를 담아두다가 화면에 나타나는 순간
data- 속성에 정의된 이미지 경로를 img src
에 쏙 넣어준다.
const images = document.querySelectorAll(".lazy");
if ("IntersectionObserver" in window) {
const options = {
root: document.querySelector(".container"),
rootMargin: "0px",
threshold: 0.5,
};
const io = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove("lazy");
io.unobserve(img);
}
});
}, options);
images.forEach((img) => {
io.observe(img);
});
코드에서 option
값에 threshold
값을 0.5로 주었는데 이 뜻은 화면에 요소가 반이상 보일때만 이미지를 로드하겠다는 뜻이다.
아래를 확인 해 볼 수 있다.
이럴때에는 이미지의 경로를 HTML data-* 속성에 넣어주는것은 같지만 부모 요소로부터 이미지의 y좌표와 윈도우의 위치를 이용해서 화면에 보여질 타이밍을 계산해서 로드한다.
핵심적인 코드만 보자면 아래와 같다.
lazyloadTimeOut = setTimeout(() => {
let scrollTop = window.pageYOffset;
images.forEach((img) => {
if (img.offsetTop < window.innerHeight + scrollTop) {
img.src = img.dataset.src;
img.classList.remove("lazy");
}
window.pageYOffset
을 통해 얼마나 스크롤 했는지 알 수 있고 window.innerHeight
을 통해 윈도우의 높이 값을 알 수 있다.
이미지의 Y좌표의 위치가 현재 화면에 보여지는 영역에 들어 왔을때
if( img.offsetTop < window.innerHeight + scrollTop)
이미지를 로드한다.
그리고 해당 로드 작업을 setTimeout함수를 이용해 비동기적으로 실행한다.
전체코드를 보자면 아래와 같다.
const lazyload = () => {
let lazyloadTimeOut = null;
if (lazyloadTimeOut) {
clearTimeout(lazyloadTimeOut);
}
lazyloadTimeOut = setTimeout(() => {
let scrollTop = window.pageYOffset;
images.forEach((img) => {
if (img.offsetTop < window.innerHeight + scrollTop) {
img.src = img.dataset.src;
img.classList.remove("lazy");
}
});
if (images.length === 0) {
document.removeEventListener("scroll", lazyload);
window.removeEventListener("resize", lazyload);
window.removeEventListener("orientationchange", lazyload);
}
});
};
document.addEventListener("scroll", lazyload);
window.addEventListener("resize", lazyload);
window.addEventListener("orientationchange", lazyload);