웹 이미지 최적화 가이드
10/30/2024
이미지는 웹상에서 글로 내용을 쉽게 전달하거나 유튜브 썸네일처럼 사람들의 시선을 사로잡아야 할 때 자주 사용된다. 하지만 빈번히 사용되는 만큼 많은 사이트에서 당신의 브라우저는 몇 가지 중요한 점을 간과한 채 이미지를 불러오고 있다.
그 몇 가지 중요한 점은 바로 사용자가 더 빠르고 쾌적하게 웹사이트를 탐색할 수 있도록 하는 중요한 최적화 방안이다. 중요한 만큼 SEO에 큰 부분을 차지하기도 한다.
그럼 대략적으로 어떤게 있는지 살펴보자.
더 높은 압축률의 이미지 포맷 활용하기
이미지는 주로 네트워크 병목 현상으로 인해 늦게 로드된다. 따라서 사용자가 눈치채지 못할 정도로 최대한 압축하여 제공하는 것이 중요하다. 현재 오픈소스 패키지로 쉽게 압축할 수 있는 이미지 포맷으로는 AVIF와 WebP가 있다. AVIF는 압축에 많은 자원이 필요하지만, 그만큼 압축률이 뛰어나다. 만약 이미지 크기가 너무 클 경우, 차선책으로 WebP 방식을 채택해도 좋다.
물론 이 방식은 브라우저의 지원이 필수적이다. 다음과 같이 HTML을 작성하면 우선 AVIF 이미지 로딩을 시도하고, 만약 지원되지 않거나 로딩에 실패할 경우 다음 포맷으로 넘어간다.
<picture>
<source srcset="/img.avif" type="image/avif">
<source srcset="/img.webp" type="image/webp">
<img src="/img.jpeg">
</picture>
아니면 그냥 포맷지정 없이 서버에서 주는 이미지를 AVIF든 WebP든 상관없이 받아 보여주는 방식이 있다.
<img src="/img">
이렇게 되면 서버는 Content-Type
헤더에 적절한 값(예: image/avif
)을 넣어 이미지를 브라우저로 전송하게 되고 브라우저는 자기가 알아서 잘 렌더링한다.
물론 이는 브라우저가 요청 시 Accept
헤더에 image/avif, image/webp
와 같이 지원하는 이미지 포맷을 함께 전송하기 때문에 서버에서 이 값을 보고 응답을 하는 것이기 때문에 가능하다.
적절한 사이즈로 불러오기
크기가 3000x2000인 이미지를 최대 가로 300px까지만 지원하는 모바일폰으로 불러오면 쓸모없는 자원 낭비다. 모바일폰에서 사진을 불러올 때는 300x200 크기의 이미지가 딱 좋을 것이다.
그럴땐 <img>
태그의 srcset
과 sizes
속성을 활용하면 된다.
<img
srcset="/img?w=300 300w,
/img?w=700 700w,
/img?w=1800 1800w"
sizes="(max-width: 300px) 300px,
(max-width: 700px) 700px,
1800px"
src="/img"
>
여기서는 sizes
속성을 먼저 살펴볼 필요가 있다. max-width
는 디스플레이의 최대 너비를 나타낸다. 따라서 300px 너비의 모바일폰은 (max-width: 300px) 300px
조건에 해당할 것이다. 이때 괄호 뒤의 300px은 해당 조건에서 이미지가 차지할 실제 크기를 의미한다.
즉, 최대 너비가 300px인 디스플레이에서 이 이미지는 300px 크기로 표시될 것임을 브라우저에 알려주는 것이다.
오케이, 그런 다음 srcset을 살펴보자. 300px에 적합한 이미지는 무엇일까? 바로 300w
라 적혀있는 부분이다. 브라우저는 300w
에 해당하는 주소인 /img?w=300
으로 이미지를 가져온다.
다른 예시를 들어보자. 내 노트북 화면이 1500px까지 지원한다 가정하면 sizes
는 1800px
이 선택되고 srcset
은 /img?w=1800
가 선택된다. 최소한 자기 화면보다 큰 사이즈를 선택하게 되는 것이다.
복잡해지는 부분
그런데 디스플레이 해상도가 1800px이라고 해서 실제로 이미지가 차지하는 부분의 크기가 1800px이 되는 것은 아니다. 실제로 이미지가 어느 정도 크기로 렌더링될지는 직접 지정해야 하는데, 이 부분은 설명이 다소 복잡해질 수 있어 별도의 글로 다루겠다.
이미지 크기가 명확히 정해져 있는 경우, "300w", "700w", "1800w"와 같은 방식 외에도 "1x", "2x", "3x"와 같은 표기법을 사용할 수 있다. 이 내용도 추후 별도의 글에서 자세히 설명하도록 하겠다.
이미지 로딩 관련 설정하기
화면 밖에 있는 이미지의 경우, 뷰포트에 들어왔을 때만 로딩을 시작하도록 설정할 수 있다. 이를 통해 초기 화면 렌더링 속도를 높이고 불필요한 네트워크 리소스 사용을 줄일 수 있다.
<img loading="lazy" >
브라우저 호환성 문제가 있다면 Intersection Observer API를 사용해 구현할 수 있다. 이 방식을 사용해서 얻는 다른 장점은 이미지가 뷰포트에 들어오기 전 적당한 선에서 미리 로딩을 시작하게 만든다면 더 사용자 친화적으로 만들 수 있다는 것이다. 물론 lazy로도 충분하다.
여러 이미지를 한 화면에 표시해야 하고 이미지 간 우선순위가 있다면, fetchpriority
속성을 활용해보자.
<img fetchpriority="high" >
<img fetchpriority="low" >
<img fetchpriority="low" >
이미지 갑자기 튀어나오는 거 방지하기
인터넷을 돌아다니다 보면 어떤 버튼을 클릭하려는 순간, 갑자기 화면이 밀려 의도치 않은 다른 것을 클릭하는 경험이 분명 있을 것이다. 특히 모바일 환경에서는 네트워크 속도로 인한 이미지 로딩 지연으로 이런 일이 자주 발생한다. 이는 브라우저가 이미지를 불러오기 전까지 그 크기를 모르기 때문에, 다른 요소들을 먼저 그린 후 이미지를 나중에 그리면서 발생하는 현상이다. 이를 Cumulative Layout Shift라고 하는데, UX에 매우 중요한 요소다.
이 현상을 방지하기 위해서는 이미지 태그에 명시적으로 크기를 지정해주는 방법이 있다.
<img src="/img" width="500" height="500">
물론 이렇게 500x500으로 지정해버리면 레이아웃 밀림 현상이 나타나지는 않지만 실제 이미지 비율과 상관없이 정사각형 모양으로 렌더링된다. CSS의 background-size
속성으로 어느 정도 만족할 수는 있겠지만 만약 실제 이미지 비율을 보여주고 싶다면 당연하게도 이미지 크기를 사전에 알아야 한다. 아니면 최소한 비율이라도 알고 있어야 한다.
<div style="position: relative; padding-bottom: 66.6667%;">
<img style="position: absolute; width: 100%; height: 100%">
</div>
padding-bottom
부분에 가로 대비 세로 비율을 적어주자.
또한 브라우저 호환성을 크게 고려하지 않아도 된다면, 대신 CSS의 aspect-ratio
속성을 활용할 수 있다.
<img style="aspect-ratio: 16/9;" >
미리보기 이미지 보여주기
가로와 세로 크기를 지정하여 공간을 확보했더라도, 실제로 사용자에게는 이미지가 로드되기 전까지 빈 공간으로만 보인다. 따라서 해당 위치에 어떤 이미지가 나올지 대략적으로 알려주는 어떠한 조치가 필요하다.
많은 사이트에서는 미리 생성한 흐리게 보이는 미리보기 이미지를 보여주는데 네트워크 요청 횟수를 줄이기 위해 HTML에 미리 데이터를 삽입하는 처리를 한다. 예를 들면 이런식으로:
<div>
<img src="data:image/jpeg;base64,...">
<img src="...">
</div>
Base64로 인코딩된 'data:image...'로 시작하는 문자열은 8x8 크기의 이미지일 경우 부담이 되지 않는 200~300바이트 정도만 차지한다. 이로 인해 브라우저는 HTML을 처음 읽자마자 즉시 미리보기 이미지를 표시할 수 있다.
결론
이 글에서는 웹 브라우저에서 이미지를 효과적으로 최적화하는 다양한 방법을 살펴보았다. 이외로 서버 측에서도 CDN을 활용하여 다양한 사이즈로 캐시된 이미지를 즉각적으로 제공하는 역할이 중요하다는 점도 언급할 만하다.