안녕하세요!
웹사이트의 첫인상을 결정짓는 가장 중요한 요소 중 하나는 바로 ‘로딩 속도’입니다. 특히 고해상도 이미지가 많은 블로그나 포트폴리오 사이트라면, 이미지가 뜰 때까지 하얀 화면만 보여주는 방식은 사용자를 금방 지치게 만듭니다. 구글 역시 LCP(Largest Contentful Paint)라는 지표를 통해 웹사이트의 로딩 성능을 엄격히 평가하고 있죠. 오늘은 라이브러리 없이 순수 자바스크립트 이미지 지연 로딩 기법을 적용하고, 이미지가 로딩되는 동안 고급스러운 느낌을 주는 ‘스켈레톤 UI’를 결합하여 내 사이트의 성능과 미학을 동시에 잡는 방법을 깊이 있게 다뤄보겠습니다.
1. 지연 로딩, 왜 loading=”lazy”만으로는 부족할까?
최신 브라우저들은 HTML 속성인 loading=”lazy”를 지원하며 매우 간편한 지연 로딩을 제공합니다. 하지만 이 기본 속성만으로는 우리가 원하는 ‘세밀한 제어’가 불가능합니다. 이미지가 나타날 때의 부드러운 페이드인 효과나, 로딩 중임을 알려주는 스켈레톤 UI를 구현하기 위해서는 결국 자바스크립트의 힘이 필요하죠.
저는 이전에 [인피니트 스크롤 구현하기] 포스팅을 통해 브라우저의 뷰포트를 감시하는 기술을 다룬 적이 있습니다. 그때 활용했던 Intersection Observer API는 자바스크립트 이미지 지연 로딩에서도 핵심적인 역할을 합니다. 단순히 이미지를 늦게 띄우는 것이 아니라, 사용자가 해당 이미지 근처에 도달했을 때만 로딩을 시작함으로써 데이터 낭비를 막고 초기 로딩 속도를 혁신적으로 개선할 수 있습니다.

2. 스켈레톤 UI: 로딩 시간을 즐겁게 만드는 마법
단순히 뱅글뱅글 돌아가는 스피너(Spinner)는 이제 구식입니다. 사용자들은 이제 로딩 중에도 콘텐츠의 ‘형태’를 미리 짐작할 수 있는 스켈레톤 UI에 더 익숙해져 있죠. 이는 심리학적으로 사용자가 대기 시간을 더 짧게 느끼게 만드는 효과가 있습니다.
제가 [바닐라 JS 상태 관리 시스템]을 설계할 때 강조했던 것처럼, UI의 변화는 데이터의 상태에 따라 유기적으로 연결되어야 합니다. ‘로딩 중(Pending)’ 상태일 때는 반짝이는 회색 박스를 보여주고, ‘로딩 완료(Success)’ 상태가 되면 실제 이미지를 부드럽게 교체하는 로직을 구축해야 합니다. 이 디테일이 바로 평범한 웹 페이지와 프리미엄 웹 서비스의 차이를 만듭니다.
3. 핵심 로직: Intersection Observer를 활용한 감시 시스템
자바스크립트 이미지 지연 로딩의 핵심은 “이미지가 화면에 들어오는 찰나를 어떻게 포착하느냐”입니다. 과거처럼 scroll 이벤트에 리스너를 주렁주렁 매다는 방식은 성능에 치명적입니다. 우리는 브라우저 최적화 API를 사용하여 효율적으로 접근해야 합니다.
// 핵심 로직: 이미지가 뷰포트에 들어올 때만 소스 로드
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // data-src에 숨겨둔 진짜 경로를 할당
img.onload = () => img.classList.add('loaded'); // 로딩 완료 시 클래스 추가
observer.unobserve(img); // 감시 종료
}
});
}, { rootMargin: '0px 0px 200px 0px' }); // 200px 미리 로드하여 끊김 방지
여기서 rootMargin을 조절하는 것이 실무적인 팁입니다. 사용자가 이미지를 보기 직전(약 200px 전)에 미리 로딩을 시작하면, 사용자는 이미지가 지연 로딩되고 있다는 사실조차 눈치채지 못할 정도로 매끄러운 경험을 하게 됩니다. 이는 마치 [자바스크립트 토스트 알림] 시스템에서 애니메이션 타이밍을 정교하게 조절했던 것과 같은 원리입니다.
4. CSS 변수와 애니메이션으로 완성하는 스켈레톤 효과
스켈레톤 UI의 생명은 부드러운 ‘반짝임’입니다. 이를 위해 자바스크립트로 일일이 스타일을 제어하기보다는, CSS 변수와 키프레임 애니메이션을 활용하는 것이 성능 면에서 훨씬 유리합니다.
/* 스켈레톤 애니메이션: 왼쪽에서 오른쪽으로 흐르는 빛 효과 */
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
}
@keyframes skeleton-loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
이 코드를 적용하면 이미지가 로드되기 전까지 우아하게 빛나는 박스가 자리를 지키게 됩니다. 이전에 [CSS 변수와 Math 객체로 구현하는 아날로그 시계] 포스팅에서 다뤘던 스타일 동적 제어 기법을 응용하면, 각 이미지의 비율에 맞는 스켈레톤 박스를 동적으로 생성할 수도 있습니다.
5. 이미지 지연 로딩 실습
지연 로딩 & 스켈레톤 실습
아래로 천천히 스크롤 해보세요
6. 실무 적용 시 주의사항: CLS(레이아웃 이동) 방지
자바스크립트 이미지 지연 로딩을 구현할 때 가장 많이 하는 실수가 바로 이미지의 높이값을 지정하지 않는 것입니다. 이미지가 뒤늦게 로딩되면서 주변 콘텐츠를 아래로 밀어버리는 현상을 CLS(Cumulative Layout Shift)라고 합니다. 이는 웹 성능 지표에 치명적인 페널티를 부여합니다.
이를 방지하기 위해 스켈레톤 UI를 담는 컨테이너에 미리 aspect-ratio나 고정 높이값을 주어야 합니다. 제가 [자바스크립트 단위 변환기] 프로젝트에서 정확한 수치 계산을 강조했던 것처럼, 웹 레이아웃에서도 숫자의 정확함이 사용자 경험을 결정합니다. 이미지가 로드되든 되지 않든, 그 자리는 항상 고정되어 있어야 합니다.
7. 네트워크 환경을 고려한 사용자 경험 디바이스 제어
자바스크립트 이미지 지연 로딩 시스템을 설계할 때 간과하기 쉬운 점이 바로 사용자의 네트워크 상태입니다. 모든 사용자가 초고속 인터넷 환경에 있는 것은 아니기 때문입니다. 특히 모바일 환경에서 데이터 절약 모드(Save Data Mode)를 사용하는 사용자를 위해 브라우저의 navigator.connection API를 활용하면 더욱 지능적인 로딩 전략을 세울 수 있습니다.
예를 들어, 4G 이상의 환경에서는 고해상도 이미지를 즉시 로드하고, 3G 이하의 느린 환경에서는 스켈레톤 UI를 더 오래 유지하거나 저해상도 이미지(LQIP, Low Quality Image Placeholder)를 먼저 보여주는 방식입니다. 이러한 세밀한 분기 처리는 단순한 코드 구현을 넘어, 전 세계 다양한 환경의 사용자를 배려하는 진정한 의미의 최적화라고 할 수 있습니다.
오늘 구현해 본 자바스크립트 이미지 지연 로딩과 스켈레톤 UI 시스템은 단순히 웹사이트를 빠르게 만드는 기술이 아닙니다. 사용자의 데이터 소비를 아껴주고, 기다림의 시간을 시각적 즐거움으로 바꿔주는 배려의 기술이죠.
프레임워크가 제공하는 편리한 기능도 좋지만, 가끔은 이렇게 바닐라 자바스크립트로 브라우저의 근본적인 API들을 직접 다뤄보는 것이 실력 향상에 큰 도움이 됩니다. 여러분의 프로젝트에도 오늘 배운 최적화 기법을 적용해 보세요.
구현 과정에서 Intersection Observer의 콜백이 예상대로 작동하지 않거나, 스켈레톤 애니메이션이 끊기는 문제가 있다면 언제든 댓글로 남겨주세요.