HEROPY
Tech
HTML IMG의 srcset과 sizes 속성(updated)
{{ scrollPercentage }}%

HTML IMG의 srcset과 sizes 속성(updated)

htmlhtml5imgsrcsetsizes

드리는 말씀

이 포스트 내용과 관련해 비슷한 질문을 자주 받는 이유로 몇 가지 저의 생각을 정리하고자 합니다.
최대한 쉬운 용어와 표현으로 정리해 보겠습니다.
도움이 되셨으면 좋겠습니다.

첫 번째, srcset 속성은 ‘이미지 소스의 세트’라는 의미입니다.
같은 비율의 다양한 크기를 가지는 동일 이미지들을 최소 2개 이상 명시하는 속성이죠.
만약 1개 이미지 소스만 명시하려면 src 속성을 사용하시면 됩니다.

두 번째, 제가 본문과 댓글에서 지속해서 강조하고 있는데,
srcset 속성은 브라우저에 이미지 선택권을 위임하는 속성으로,
개발자는 어느 환경에서 어떤 크기의 이미지가 선택될 것인지 알 필요가 없으며, 알더라도 그냥 모르는 척하는 것이 정신 건강에 좋습니다.
수많은 사용자 환경의 각 브라우저는 사용자에게 최적 크기의 이미지를 보여주기 위해 항상 노력하고 있습니다.
그러다 보니 대표적으로 브라우저가 큰 이미지를 한 번 불러왔으면, 오히려 해상도가 떨어지는 작은 이미지를 또 불러올 필요를 느끼지 못해 뷰포트 크기가 변경되어도 이미지 갱신이 일어나지 않는다고 보시면 됩니다.
이미지를 불러오는 행위는 네트워크, 메모리, 성능, 시간 등의 여러 비용을 지불해야 하기 때문입니다.
그런 이유에서 앞서 ‘같은 비율의 다양한 크기를 가지는 동일 이미지들’이란 표현을 사용했고요.

세 번째, 다른 비율의 다양한 크기의 다른 이미지들을 사용하고 싶다면,
IMG 요소의 srcset 속성이 아닌 CSS의 @media(미디어쿼리 혹은 미디어 규칙) 사용을 권장합니다.
개발자가 원하는 해상도에 맞게 출력할 결과를 정확히 명시할 수 있기 때문이죠.
기타 이유는 ‘두 번째’ 설명과 같습니다.

네 번째, xw 디스크립터는 개발자가 브라우저에 이미지의 크기를 알려주는 용도의 단위입니다.
‘두 번째’ 설명에서 말씀드린대로 이미지 로드는 지불할 비용이 크기 때문에,
브라우저가 비용 지불 없이도 이미지의 크기를 알 수 있도록 개발자가 x 혹은 w 디스크립터로 이미지 크기를 명시하는 것입니다.
따라서 이미지의 정확한 크기를 확인하고 입력하는 것이 중요합니다.

다섯 번째, Mac에서는 많은 경우 디스플레이 해상도로 ‘스케일링’을 사용합니다.
쉽게 말씀드리면, 디스플레이 확대/축소 비율이 100%가 아니고 120%, 150% 같은 확대된 해상도라는 겁니다.
Windows도 같은 기능이 있습니다만 기본 옵션이 아닌 경우가 많아 말씀드리고자 하는 현상이 덜합니다.
모두 OS 디스플레이 옵션에서 해당 내용을 확인할 수 있는데요.
특히 Mac의 경우 정확한 해상도 수치로 옵션 선택이 안되다 보니 문제 해결과 이해에 어려움을 겪는 경우가 많은 듯합니다.
그래서 Mac에서는 RDM(https://github.com/avibrazil/RDM) 같은 프로그램을 사용하시는 것도 좋습니다.
아무튼 디스플레이가 확대/축소된 경우, 이미지도 그에 맞게 확대/축소된 크기로 렌더링을 해야 하는데요.
그러다 보면 확대/축소 일부 구간에서 이미지가 흐려지는 현상이 발생합니다.
대부분의 이미지는 비트맵 방식(jpg, png, gif 등)이고 픽셀 단위로 렌더링 되다 보니 발생하는 현상입니다.
그런데 일부 브라우저는 이 현상을 해결하기 위해 이미지 비율을 임의로 조정합니다.
예를 들어, 141% 확대된 이미지라면 150%로 출력한다는 것이죠.
그래서 해당 뷰포트 크기에서 원하는 이미지가 나오지 않을 수 있는 겁니다.
그 외 ‘두 번째’ 내용도 같이 발생할 수 있고요.

여섯 번째, xw 디스크립터는 필요에 맞게 사용하는 것이지, 더 좋고 나쁨의 단위는 아닙니다.
제가 아는 바로는 w 디스크립터가 비교적 더 최신 기술이고, 그 전에 많이 사용된 x 디스크립터에 관련된 정보가 훨씬 많습니다.
아이폰이 3에서 4로 넘어가면서 3과 4를 동시에 지원해야 하니 iOS 개발에 필요한 이미지를 특정 배수 크기로 요구하기 시작했고,
(예를 들어 image.pngimage@2x.png를 동시에 관리해야 했죠)
이후 수많은 모바일 개발에서 배수 방식을 사용하면서 x 디스크립터가 유용해졌죠.
그리고 그것을 API로 요구하는 경우도 있고요.
그래서 xw 디스크립터 사용에 대한 입장이 일부 나뉘는 듯한데요, 저의 생각은 이렇습니다.
‘일반적인 웹 개발 경우에 꼭 배수(x)로 작성할 필요 없이 그냥 w 디스크립터로 다양한 이미지를 제공하면, 브라우저가 이미지를 선택할 권한이 있으니 알아서 필요한 이미지를 사용하지 않을까?! 단, 개발 요구사항이 x 디스크립터라면 필수로 작성해야지!’
수많은 사용자 환경이 동일한 배수 이미지를 요구하는 것이 아니기 때문에 개발자는 모든 환경에 대응한 이미지를 준비할 수 없고 그만큼 w 디스크립터가 중요해졌다고 생각합니다.
그리고 w는 ‘Width’를 의미하는데요, 그러면 ‘Height’를 의미하는 h 디스크립터는 없느냐?
실제 h 디스크립터 도입을 논의하긴 했지만, 여러 문제로 폐기되었는데요, 그 문제 중 하나가 바로 사용과 인지의 복잡성입니다.
결론적으로 개발자는 w 디스크립터를 사용해 이미지의 가로 너비만 명확하게 명시하면, 수많은 사용자 환경에 대한 대응은 브라우저가 알아서 할 것이니 고민을 덜 해도 된다는 겁니다.

여기까지가 댓글과 이메일을 통해 자주 받는 질문에 대한 저의 생각입니다.
여러 정보를 바탕으로 지극히 주관적으로 풀어낸 답변이니, 무조건적인 신뢰는 금물입니다.
만약 위와 다른 의견이나 정보가 있으시다면 댓글이나 이메일 부탁드립니다.
감사합니다.

변경사항

2020년 4월

  • px단위와 혼동되지 않게, xw descriptor의 ‘단위’ 단어를 ‘디스크립터’로 변경했습니다.
  • X descriptor 파트에 ‘내 디바이스 화면 측정’ 링크를 추가했습니다.
  • 일부 내용과 오타를 수정했습니다.

개요

일반적으로 반응형 웹에서 이미지를 지원하기 위해, ‘미디어쿼리’라고 부르는 CSS Media Rule(@media)에서 background-image 속성을 많이 사용하는데, 반응형 이미지를 처리하기 위해 뷰포트(Viewport)의 크기부터 사용자 화면의 해상도 등 많은 환경을 고려해야 합니다.
하지만 우리는 HTML IMG의 srcsetsizes를 통해 쉽게는 이미지의 크기를 설정하는 것만으로 대부분의 고려 사항을 사용자 브라우저(User agent)에 떠넘길 수 있습니다.

테스트에 앞서..

테스트를 위해 우선 확인할 부분이 있습니다.
이미지가 캐싱 되어 새로고침 되지 않는 경우가 발생할 수 있기 때문에, (크롬 브라우저를 사용한다면) 개발자 도구(F12)에서 disable cache를 체크하세요!

disable cache

그리고 예제에 사용한 이미지들은 포스트 하단에 첨부했습니다.

간단한 예제

<img
  srcset="images/heropy_small.png 400w,
          images/heropy_medium.png 700w,
          images/heropy_large.png 1000w"     
  sizes="(max-width: 500px) 444px,
         (max-width: 800px) 777px,
         1222px"
  src="images/heropy.png"
  alt="HEROPY" />

위 예제는 다음과 같이 이해할 수 있습니다.
srcset 속성은 쉼표(,)로 구분된 사용할 이미지들의 경로와 해당 이미지의 원본 크기를 지정하고,
sizes 속성은 쉼표(,)로 구분된 미디어조건(선택적)과 그에 따라 최적화되어 출력될 이미지 크기를 지정합니다.

<img
  srcset="경로 원본크기,
          경로 원본크기,
          경로 원본크기"
  sizes="(미디어조건) 최적화출력크기,
         (미디어조건) 최적화출력크기,
         기본출력크기"
  src="경로"
  alt="대체텍스트" />

일반적인 IMG 작성 방식이 아니기 때문에 조금 생소합니다.
하지만 어렵게 생각할 필요가 없습니다!
차근차근 알아봅시다.

src 속성은 srcset을 사용할 수 없는 환경에서 동작합니다!

srcset

srcset은 브라우저에 제시할(사용할) 이미지들과 그 이미지들의 원본 크기를 지정합니다.

사용 방법은 간단합니다.

사용할 이미지를 사이즈별로 2장 이상 준비하여 srcset 속성에 작성합니다.
단, 주의사항은 이미지의 크기로 px단위가 아닌 w 디스크립터 혹은 x 디스크립터를 입력해야 하며, 작은 크기 이미지부터 순서대로 입력합니다.

W descriptor

w 디스크립터(Width descriptor)는 이미지의 원본 크기(가로 너비)를 의미합니다.
예를 들어 400x300(px) 크기 이미지의 w 값은 400w입니다.

브라우저(User agent)는 지정된 w 디스크립터를 통해 각 이미지의 최적화된 픽셀 밀도를 계산합니다.

다음 예제를 살펴봅시다.

<img
  srcset="images/heropy_small.png 400w,
          images/heropy_medium.png 700w,
          images/heropy_large.png 1000w"
  src="images/heropy.png"
  alt="HEROPY" />

위 예제의 결과로,
뷰포트 너비가 400px 이하일 때 heropy_small.png(400px)가 사용됩니다.
뷰포트 너비가 401~700px 일 때 heropy_medium.png(700px)가 사용됩니다.
뷰포트 너비가 701px 이상일 때 heropy_large.png(1000px)가 사용됩니다.

이하 모든 예제의 뷰포트 너비는 뷰포트의 가로 너비를 의미합니다.

srcset example 1

우리는 단지 3장의 이미지와 그 크기만 srcset에 입력했을 뿐인데 브라우저는 각 이미지 중 현재 뷰포트 너비에 최적화된 이미지를 선택해 출력합니다.
마치 다음의 CSS 미디어조건과 비슷합니다.

.some-image {
  width: 400px;
  height: 400px;
  background-image: url("images/heropy_small.png");   
  background-repeat: no-repeat;
}
@media (min-width: 401px) {
  .some-image {
    width: 700px;
    height: 700px;
    background-image: url("images/heropy_medium.png");   
  }
}
@media (min-width: 701px) {
  .some-image {
    width: 1000px;
    height: 1000px;
    background-image: url("images/heropy_large.png");   
  }
}

고정된 이미지 크기를 유지하려면 width 속성을 추가할 수 있습니다.
(sizes 속성과는 다른 개념입니다!)

<img
  srcset="images/heropy_small.png 400w,
          images/heropy_medium.png 700w,
          images/heropy_large.png 1000w"
  width="400"
  src="images/heropy.png"
  alt="HEROPY" />

srcset example 2

이는 다음과 비슷합니다.

.some-image {
  width: 400px;
  height: 400px;
  background-image: url("images/heropy_small.png");   
  background-repeat: no-repeat;
  background-size: cover;
}
@media (min-width: 401px) {
  .some-image {
    background-image: url("images/heropy_medium.png");   
  }
}
@media (min-width: 701px) {
  .some-image {
    background-image: url("images/heropy_large.png");   
  }
}

X descriptor

x 디스크립터(Device pixel ratio descriptor)는 이미지의 비율 의도를 의미합니다.
w 디스크립터에서 사용했던 예제를 다음과 같이 수정할 수 있습니다.

<img
  srcset="images/heropy_small.png 1x,
          images/heropy_medium.png 1.75x,
          images/heropy_large.png 2.5x"
  src="images/heropy.png"
  alt="HEROPY" />

x 디스크립터는 디바이스의 픽셀 비율(Device pixel ratio)과 일치하는 값으로 최적화 선택됩니다.
mydevice.io에서 현재 화면의 측정 값을 확인할 수 있습니다.

일반적으로 정수(integer) 값으로 제공하는 것이 좋습니다.

w 디스크립터를 사용하면 x 디스크립터를 사용하지 않아도 됩니다.
많은 경우 w 디스크립터의 사용을 추천합니다.

sizes

sizes는 미디어조건과 그 조건에 해당하는 이미지의 ‘최적화 출력 크기’를 지정합니다.

다음 예제를 살펴봅시다.

<img
  srcset="images/heropy_small.png 400w,
          images/heropy_medium.png 700w,
          images/heropy_large.png 1000w"
  sizes="(min-width: 1000px) 700px"
  src="images/heropy.png"
  alt="HEROPY" />

위 예제의 결과로,
뷰포트 너비가 400px 이하일 때 heropy_small.png(400px)가 사용됩니다.
뷰포트 너비가 401~700px 일 때 heropy_medium.png(700px)가 사용됩니다.
뷰포트 너비가 701~999px 일 때 heropy_large.png(1000px)가 사용됩니다.
뷰포트 너비가 1000px 이상일 때 heropy_medium.png(700px)가 사용됩니다.

srcset example 3

sizes="(min-width: 1000px) 700px"에서 (min-width: 1000px)은 ‘뷰포트 너비(가로)가 1000px 이상일 때’를 의미하며, 이어나오는 700px은 그 조건일 때 이미지를 ‘700px로 최적화 출력하겠다’를 의미합니다.
그렇다면 700px로 이미지를 출력하기 위해 srcset 목록에서 사용될 최적의 이미지는 heropy_medium.png이며, 결과로 뷰포트 너비(가로)가 1000px 이상일 때 heropy_medium.png가 사용되었습니다.

다음 예제도 살펴봅시다.
이번엔 미디어조건을 생략했네요.

<img
  srcset="images/heropy_small.png 400w,
          images/heropy_medium.png 700w,
          images/heropy_large.png 1000w"
  sizes="500px"
  src="images/heropy.png"
  alt="HEROPY" />

위 예제의 결과로,
뷰포트 너비와 상관없이(‘헛 상관이 없다고?!’) heropy_medium.png만 사용됩니다.
또한 heropy_medium.png500px의 크기를 가집니다.(원래는 700px 크기의 이미지입니다)

srcset example 4

왜 뷰포트 너비와 상관없이 하나의 이미지만 사용되는지 아직 확실치 않지만, 다음 예제와 비교해 봅시다.
이번엔 sizes는 없고 width가 있네요.

<img
  srcset="images/heropy_small.png 400w,
          images/heropy_medium.png 700w,
          images/heropy_large.png 1000w"
  width="500"
  src="images/heropy.png"
  alt="HEROPY" />

위 예제의 결과로,
뷰포트 너비가 400px 이하일 때 heropy_small.png가 사용됩니다.
뷰포트 너비가 401~700px 일 때 heropy_medium.png가 사용됩니다.
뷰포트 너비가 701px 이상일 때 heropy_large.png가 사용됩니다.
뷰포트 너비에 따라 사용되는 이미지가 달라지지만 크기는 500px로 고정되었습니다.

srcset example 5

그럼 이해가 되시나요?!
width는 이미지의 ‘출력 크기’만 지정하는 데 반해, sizes는 이미지의 ‘출력 크기’ + ‘최적 크기’도 함께 지정하는 개념입니다.
따라서 sizes="500px"이 지정된 첫번째 예제는 500px에 최적화된 이미지로 heropy_medium.png를 사용했고 이미지 크기도 500px로 설정한 것이죠.

sizeswidth를 같이 작성할 경우 width가 우선합니다.

개념이 이해되었다면, 다음과 같이 여러 조건을 작성할 수도 있습니다.
쉼표(,)를 사용해 구분한다는 것을 주의하세요.

<img
  srcset="images/heropy_small.png 400w,
          images/heropy_medium.png 700w,
          images/heropy_large.png 1000w"
  sizes="(min-width: 701px) 1000px,
         (min-width: 401px) 700px,
         400px"
  src="images/heropy.png"
  alt="HEROPY" />

브라우저 지원

srcsetsizes는 IE를 지원하지 않습니다.

https://caniuse.com/#feat=srcset

brower support for srcset and sizes attributes

Polyfill

https://github.com/aFarkas/respimage

Sample images

HEROPY
HEROPY
HEROPY
HEROPY

참고 자료(References)

https://developer.mozilla.org/ko/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images
https://stackoverflow.com/questions/40890825/explain-w-in-srcset-of-image
https://github.com/ResponsiveImagesCG/picture-element/issues/85
https://stackoverflow.com/questions/26928828/html5-srcset-mixing-x-and-w-syntax

공지 이미지
이 내용을 72시간 동안 안 보고 싶어요!?