블러 placeholder를 사용한 Lazy Loading(GIF 화질 때문에 블러 이미지가 뭉개져서 보이는 점 참고)
웹페이지에서 성능에 영향을 가장 많이 주는 부분이 이미지 / 비디오 같은 미디어 요소다. 특히 이미지는 배너, 제품 사진, 로고 등 페이지 구석구석에서 사용한다. HTTP Archive Data에 따르면 전체 웹페이지 용량의 45%
를 이미지가 차지한다고 한다. 이미지를 사용하지 않는건 불가능하지만, 화면에 노출될 때만 이미지를 불러오는 방식으로 페이지 로딩 시간을 단축시킬 수 있다. 이런 방식을 Lazy Loading이라고 한다.
<aside> 💡 Lazy Loading이 적용된 이미지가 뷰포트에 근접해서 이미지를 로드하면 콘텐츠가 밀려나는 현상이 발생한다. 이를 방지하려면 이미지를 감싸는 컨테이너 요소에 높이 / 너비를 지정하면 된다.
</aside>
Lazy Loading은 크게 Chrome Native 방식과 JavaScript를 이용한 방식으로 구현할 수 있다.
Chrome 76 버전 이상부터 Native 방식의 Lazy Loading을 지원한다. 적용할 이미지 태그의 loading
속성에 lazy
만 추가하면 된다. 사용법은 간단하지만 76버전 이상의 크롬 브라우저에서만 적용되는 단점이 있다.
<img src="example.jpg" loading="lazy" width="200" height="200" alt="..." />
loading
속성을 사용하지 않았을 때의 기본값으로 lazy
속성을 적용한 것과 동일.이미지 태그의 src
속성엔 로딩 전 보여질 placeholder 이미지를 추가해두고, 데이터 프로퍼티를 이용해 원본 이미지를 data-src
속성에 할당해둔다. 이미지 태그가 화면에 노출되면 data-src
속성에 지정해놓은 원본 이미지를 src
속성에 할당해서 원본 이미지를 로드한다.
<!-- 화면 노출 전 -->
<img data-src="원본 이미지 주소" src="placeholder 이미지 주소" />
<!-- 화면 노출 후 -->
<img data-src="원본 이미지 주소" src="원본 이미지 주소" />
위 방식을 구현하려면 이미지 태그의 화면 노출 여부를 확인해야된다. 구현 방법은 아래 2가지가 있다.
스크롤 이벤트가 발생할 때마다 요소가 화면에 노출됐는지 확인하는 방식. getBoundingClientRect
메서드를 호출해서 뷰포트 기준의 요소 위치 좌표값을 얻어야 한다. 이 메서드를 호출할 때마다 리플로우가 발생하는 단점이 있으며, 마우스를 스크롤할 때마다 이벤트가 계속 호출되므로 스로틀도 적용해야 된다.
// 요소가 화면에 완전히 들어왔는지 확인하는 함수
function checkInView(el) {
// 주어진 요소의 뷰포트 기준 위치 및 크기 반환
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 && // 뷰포트 상단보다 아래 있는지 여부
rect.left >= 0 && // 뷰포트 왼쪽보다 오른쪽에 있는지 여부
rect.bottom <= window.innerHeight && // 뷰포트 하단 이내에 있는지 여부
rect.right <= window.innerWidth // 뷰포트 오른쪽 이내에 있는지 여부
);
}
window.addEventListener('scroll', () => {
document.querySelectorAll('.lazy-img').forEach((image) => {
if (checkInView(image)) {
image.src = image.dataset.src; // 이미지 태그가 화면에 노출되면 src 속성에 원본주소 할당
}
});
});