프로젝트가 어느정도 마무리 되고 시간적인 여유가 생겨 웹 성능 최적화를 위해 ImagelazyLoaidng을 적용해보았다.
1) 해당 페이지의 모든 이미지를 처음에 다운로드 하지 않으므로 페이지를 빠르게 로드할 수 있을거고
2) 페이지가 빠르게 로드 되는 만큼 사용자는 콘텐츠를 더 빠르게 볼 수 있을거라 생각했다.
3) 물론 나는 lazyLoading가 웹 성능 최적화에 쓰인다길래 한 번 적용해보고 싶어던 마음이 크다 힣
lazyLoading을 구현할 때 Intersection Observer라는 Web API를 많이 사용한다길래 나도 해당 API를 활용해서 구현해봤다.
Intersection Observer로 요소의 관찰자를 등록하고, 요소가 화면에 나타날 때 수행할 작업을 작성해주면 끘!
개발자도구 > network창 > img 탭 눌러 확인해보면 이미지를 한 번에 불러오는게 아닌 필요한 시점에 불러오는걸 확인할 수 있다. 👏🏻
Intersection observer는 내가 설정한 요소(Element)가 화면에 지금 보이는 요소인지 아닌지를 구별하는 기능을 제공한다.
new IntersectionObserver()를 통해 관찰자(Observer)를 생성하고 observe메서드를 사용해서 관찰할 대상(Element)을 지정한다.
const io = new IntersectionObserver(callback, options) // 관찰자 초기화
io.observe(element) // 관찰할 대상(요소) 등록
https://heropy.blog/2019/10/27/intersection-observer/ 글을 참고!
intersection Observer에 대해 아주 간략하게 적어놨다.
더 궁금한게 생기면 해당 블로그에서 찾아보면 될 듯!
new IntersectionObserver() 로 생성자를 만들어주는데, 2개의 인수 callback과 options을 정해줘야한다.
IntersectionObserver의 인스턴스에서 callback함수가 호출되는 상황을 정의해주면 된다.
// options 설정
const options = {
root: null, // null일 경우 브라우저 viewport
// root: document.querySelector('.class이름') class를 가진 엘리먼트를 root로 설정하고싶을때
rootMargin: [0,0,'100px',0], // 아래로 100px까지는 관찰 대상으로 간주
threshold: 0.5 /// 관찰 대상의 50% 이상이 보일 때 콜백 호출
}
관찰할 대상이 등록되거나, 가시성에 변화가 생기면 관찰자는 callback을 실행한다.
나는 isIntersecting 이라는 속성을 사용해서 내가 설정한 Option에 해당되는 교차상태에 따라 코드가 실행되게 만들었다.
// IntersectionObserver 생성
const io = new IntersectionObserver((entries) => {
// IntersectionObserverEntry 객체 리스트와 observer 본인(self)를 받음
// 동작을 원하는 것 작성
entries.forEach(entry => {
// 관찰 대상이 viewport 안에 들어온 경우 image 로드
if (entry.isIntersecting) {
// data-src 정보를 타켓의 backgroundImage 속성에 설정
loadBackgroundImage()
// 이미지를 불러왔다면 타켓 엘리먼트에 대한 관찰을 멈춘다.
observer.unobserve(entry.target);
}
}
}, options)
나는 loadBackgroundImage() 메소드를 만들어 내가 설정한 요소가 화면에 보일 때 실행할 로직을 만들었다.
loadBackgroundImage() 메소드의 내용은
설정한 요소에 dataset을 활용해서 이미지를 불러올 URL을 설정해놨는데,
관찰 대상이 교차상태가 되었을 때 해당 요소의 className이 'lazy'라면 설정한 dataset 정보를 가져와 backgroundImage로 설정하는 내용이다.
// HTML
<div
:data-imageUrl="item[fieldNames.imageUrl] ? item[fieldNames.imageUrl] : ''">
// loadBackgroundImage()
const loadBackgroundImage = () => {
if (el.className.includes('lazy')) {
// 선택한 요소의 클래스가 'lazy'라면 dataset에서 imageURL 데이터를 가져와 backgroundImage로 설정해준다.
const imageUrl = el.dataset.imageurl;
el.style.backgroundImage = `url(${imageUrl})`;
}
};
export default {
mounted(el) {
const loadBackgroundImage = () => {
if (el.className.includes('lazy')) {
const imageUrl = el.dataset.imageurl;
el.style.backgroundImage = `url(${imageUrl})`;
}
};
// Intersection Observer를 지원하는 브라우저의 경우
// Observer 관찰자 만들어
// 스크롤에 따라 이미지 로딩할 수 있도록 만들어줌
const createObserver = () => {
const observerOption = {
root: null, // 뷰포트를 기준으로 관찰
rootMargin: '0px 0px 100px 0px', // 아래로 100px까지는 관찰 대상으로 간주
threshold: 0.5, // 관찰 대상의 50% 이상이 보일 때 콜백 호출
};
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadBackgroundImage();
observer.unobserve(el);
}
});
}, observerOption);
observer.observe(el)
};
// Intersection Observer를 지원하지 않는 브라우저의 경우
// backgroundImage에 바로 이미지 로딩시켜줌
window['IntersectionObserver']
? createObserver()
: loadBackgroundImage();
},
};
위와 같이 적용을 하면
1. ImagelazyLoaidng을 적용하기로 지정한 요소가 뷰포트내에 보이기 시작하면
2. loadBackgroundImage()가 실행되면서 지정한 요소내의 dataset에 담겨있는 이미지 URL 경로를 가져와 el.style.backgroundImage = url(${imageUrl})
처럼 css를 변경한다.
3. 변경된 backgroundImage의 이미지 주소에서 이미지가 로딩되면서 ImagelazyLoaidng이 구현된다.
vue 프로젝트와 관련해서 lazyLoading 하는 방법을 찾다보니
cumstomDirective를 통해 lazyLoading을 구현하는 방법도 있길래 한 번 적용해봤다.
이건 길어지니까 따로 링크 걸어두는걸루!
🔗 vue3 customDirective 적용기