HTML의 picture 요소로 상황에 맞는 이미지 제공하기

HTML의 picture 요소로 상황에 맞는 이미지 제공하기

웹 페이지에서 이미지를 다룰 때 한 가지 파일로 모든 상황을 커버하기란 쉽지 않습니다. 모바일에서는 세로로 길쭉한 이미지가 잘 어울리는데 데스크톱에서는 가로로 넓은 이미지가 필요할 수 있고, 최신 브라우저에서는 AVIF로 용량을 줄이고 싶지만 구형 브라우저에서는 JPEG로 보여줘야 하죠. <img> 태그 하나로는 이런 분기를 처리할 수가 없습니다.

HTML의 <picture> 요소는 바로 이런 문제를 해결하기 위해 등장했는데요. 화면 크기, 이미지 포맷 지원 여부, 해상도 등 여러 조건에 따라 브라우저가 최적의 이미지를 골라서 보여주도록 할 수 있습니다. 이 글에서는 <picture> 요소의 기본 구조부터 실전에서 자주 쓰이는 패턴까지 하나씩 짚어보겠습니다.

기본 구조

<picture> 요소는 크게 세 부분으로 구성됩니다.

<picture>
  <source media="(min-width: 800px)" srcset="wide.jpg" />
  <source media="(min-width: 480px)" srcset="medium.jpg" />
  <img src="small.jpg" alt="풍경 사진" />
</picture>

<picture> 요소 안에 하나 이상의 <source> 요소와 반드시 하나의 <img> 요소를 넣는 구조입니다. 브라우저는 <source> 요소를 위에서부터 순서대로 확인하면서 조건에 맞는 첫 번째 것을 선택합니다. 어떤 <source>도 조건에 맞지 않으면 마지막의 <img> 요소가 폴백(fallback)으로 사용됩니다.

여기서 중요한 점이 있는데요. <img> 요소는 단순한 폴백이 아니라 실제로 이미지를 화면에 렌더링하는 역할을 합니다. <picture><source>는 어떤 이미지 소스를 쓸지 결정만 해줄 뿐이고, 최종적으로 화면에 그려지는 것은 항상 <img> 요소입니다. 그래서 alt 속성이나 width, height, loading 같은 속성은 <img> 요소에 넣어야 합니다.

<picture>
  <source media="(min-width: 800px)" srcset="wide.jpg" />
  <img
    src="small.jpg"
    alt="풍경 사진"
    width="600"
    height="400"
    loading="lazy"
  />
</picture>

<picture> 요소를 지원하지 않는 아주 오래된 브라우저에서는 <picture><source> 태그를 무시하고 <img> 요소만 해석하기 때문에 자연스럽게 폴백 이미지가 표시됩니다. 별도로 하위 호환성을 신경 쓰지 않아도 되는 거죠.

아트 디렉션

<picture> 요소가 가장 빛나는 순간은 화면 크기에 따라 아예 다른 이미지를 보여주고 싶을 때입니다. 이를 “아트 디렉션(art direction)“이라고 부르는데요. 같은 이미지를 단순히 축소하는 것이 아니라, 화면에 맞게 구도나 크롭 자체를 바꾸는 접근입니다.

예를 들어 온라인 쇼핑몰의 배너를 생각해 보겠습니다.

<picture>
  <source media="(min-width: 1024px)" srcset="banner-desktop.jpg" />
  <source media="(min-width: 640px)" srcset="banner-tablet.jpg" />
  <img src="banner-mobile.jpg" alt="여름 세일 배너" />
</picture>

데스크톱에서는 가로로 넓은 전체 배너를 보여주고, 태블릿에서는 핵심 문구 위주로 살짝 잘라낸 버전을, 모바일에서는 세로 비율의 간결한 이미지를 보여주는 식입니다. <img> 태그의 srcset 속성만으로는 같은 이미지의 해상도만 바꿀 수 있을 뿐 이렇게 구도 자체를 바꾸는 건 불가능합니다.

CSS 미디어 쿼리와 마찬가지로 <source>media 속성에도 같은 미디어 조건을 쓸 수 있습니다.

<picture>
  <!-- 가로 모드일 때 -->
  <source media="(orientation: landscape)" srcset="landscape.jpg" />
  <!-- 세로 모드일 때 -->
  <source media="(orientation: portrait)" srcset="portrait.jpg" />
  <img src="default.jpg" alt="적응형 이미지" />
</picture>

화면 방향(orientation), 해상도(resolution), 색상 범위(color-gamut) 등 CSS에서 쓸 수 있는 미디어 특성이라면 거의 다 사용 가능합니다.

한 가지 주의할 점은 <source> 요소의 순서입니다. 브라우저는 위에서부터 순서대로 조건을 확인하기 때문에 넓은 화면 조건을 먼저 쓰는 게 일반적입니다. 좁은 화면 조건을 위에 두면 데스크톱에서도 모바일 이미지가 선택되어 버릴 수 있거든요.

<!-- ❌ 잘못된 순서 -->
<picture>
  <source media="(min-width: 480px)" srcset="medium.jpg" />
  <source media="(min-width: 1024px)" srcset="large.jpg" />
  <img src="small.jpg" alt="풍경" />
</picture>

<!-- ✅ 올바른 순서: 넓은 화면부터 -->
<picture>
  <source media="(min-width: 1024px)" srcset="large.jpg" />
  <source media="(min-width: 480px)" srcset="medium.jpg" />
  <img src="small.jpg" alt="풍경" />
</picture>

이미지 포맷 분기

<picture> 요소가 잘하는 또 다른 일은 브라우저가 지원하는 이미지 포맷에 따라 다른 파일을 제공하는 것입니다. <source> 요소의 type 속성으로 MIME 타입을 지정하면 브라우저가 자신이 해석할 수 있는 포맷을 자동으로 선택합니다.

<picture>
  <source srcset="photo.avif" type="image/avif" />
  <source srcset="photo.webp" type="image/webp" />
  <img src="photo.jpg" alt="사진" />
</picture>

이 코드에서 브라우저는 AVIF를 지원하면 photo.avif를, AVIF는 안 되지만 WebP는 되면 photo.webp를, 둘 다 안 되면 photo.jpg를 내려받습니다. 같은 품질 기준으로 AVIF는 JPEG보다 50% 가까이, WebP는 25~35% 정도 파일 크기가 작으니까 네트워크 비용을 꽤 줄일 수 있습니다.

실제로 이미지가 많은 사이트에서 이 패턴을 적용하면 체감할 수 있을 정도로 로딩이 빨라집니다. Astro의 Picture 컴포넌트도 내부적으로 이 방식을 사용해서 여러 포맷의 이미지를 자동 생성하고 <picture> 요소로 감싸줍니다.

포맷 분기와 아트 디렉션을 동시에 사용할 수도 있습니다.

<picture>
  <!-- 넓은 화면 + 최신 포맷 -->
  <source
    media="(min-width: 800px)"
    srcset="hero-wide.avif"
    type="image/avif"
  />
  <source
    media="(min-width: 800px)"
    srcset="hero-wide.webp"
    type="image/webp"
  />
  <source media="(min-width: 800px)" srcset="hero-wide.jpg" />

  <!-- 좁은 화면 + 최신 포맷 -->
  <source srcset="hero-narrow.avif" type="image/avif" />
  <source srcset="hero-narrow.webp" type="image/webp" />

  <img src="hero-narrow.jpg" alt="히어로 이미지" />
</picture>

<source> 요소가 많아져서 코드가 길어 보이지만 브라우저는 조건에 맞는 첫 번째 소스만 내려받으니 불필요한 네트워크 비용은 들지 않습니다.

해상도에 따른 이미지 분기

요즘 스마트폰이나 노트북에는 고밀도 디스플레이(Retina 등)가 많이 탑재되어 있죠. 화면의 물리적 크기는 같아도 실제 픽셀 수가 2배, 3배인 기기에서는 그에 맞는 고해상도 이미지를 보여줘야 선명하게 보입니다.

<source> 요소의 srcset 속성에서 픽셀 밀도 서술자(x)를 사용하면 기기의 화면 밀도에 맞는 이미지를 제공할 수 있습니다.

<picture>
  <source
    media="(min-width: 800px)"
    srcset="hero-wide.jpg 1x, hero-wide-2x.jpg 2x"
  />
  <img
    src="hero-mobile.jpg"
    srcset="hero-mobile.jpg 1x, hero-mobile-2x.jpg 2x"
    alt="히어로 이미지"
  />
</picture>

일반 디스플레이에서는 1x 이미지를, Retina 같은 고밀도 디스플레이에서는 2x 이미지를 선택합니다. 3x까지 준비하면 더 좋지만 이미지 용량이 커지니까 보통은 2x까지만 준비하는 것이 현실적입니다.

너비 서술자(w)를 쓰면 브라우저가 더 유연하게 판단할 수 있습니다.

<picture>
  <source
    media="(min-width: 800px)"
    srcset="wide-400.jpg 400w, wide-800.jpg 800w, wide-1200.jpg 1200w"
    sizes="(min-width: 1200px) 1200px, 100vw"
  />
  <img
    src="narrow-400.jpg"
    srcset="narrow-400.jpg 400w, narrow-800.jpg 800w"
    sizes="100vw"
    alt="반응형 이미지"
  />
</picture>

srcset에서 400w, 800w, 1200w는 각 이미지 파일의 실제 픽셀 너비를 알려줍니다. sizes 속성은 이미지가 화면에서 차지할 예상 너비를 브라우저에게 알려주는 역할이고요. 브라우저는 이 두 정보와 기기의 화면 밀도를 조합해서 가장 적절한 이미지를 스스로 선택합니다.

picture vs img srcset

<picture> 요소 없이 <img> 태그의 srcset 속성만으로도 반응형 이미지를 구현할 수 있습니다. 그렇다면 언제 <picture>를 쓰고 언제 <img srcset>만으로 충분한지 혼동이 올 수 있는데요.

<img> 태그의 srcset은 같은 이미지를 여러 해상도로 제공할 때 적합합니다.

<img
  src="photo-400.jpg"
  srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
  sizes="(min-width: 800px) 50vw, 100vw"
  alt="사진"
/>

이 경우 브라우저에게 선택권을 완전히 넘기는 겁니다. 화면 크기, 픽셀 밀도, 네트워크 상태 등을 종합적으로 고려해서 브라우저가 알아서 최적의 이미지를 고릅니다. 때로는 개발자가 기대한 것과 다른 이미지를 선택할 수도 있지만 브라우저가 판단하기에 그게 사용자에게 최선인 겁니다.

반면 <picture> 요소는 개발자가 명확한 규칙을 정해주는 방식입니다.

기능<img srcset><picture>
같은 이미지의 해상도 분기
이미지 포맷 분기 (AVIF, WebP)
아트 디렉션 (구도 변경)
브라우저 선택권브라우저가 자유롭게 결정개발자가 규칙 지정
코드 복잡도간단상대적으로 복잡

정리하면 같은 이미지를 크기만 다르게 제공하는 거라면 <img srcset>으로 충분합니다. 하지만 화면 크기에 따라 아예 다른 구도의 이미지를 보여주거나, AVIF/WebP 같은 최신 포맷 지원 여부에 따라 분기해야 한다면 <picture> 요소가 필요합니다.

접근성과 SEO

<picture> 요소를 사용할 때 접근성이나 SEO 관점에서 추가로 신경 쓸 부분이 있을까요? 다행히 크게 달라지는 건 없습니다.

alt 속성은 <img> 요소에만 넣으면 됩니다. <picture><source>는 보조 기술(스크린 리더 등)에서 직접 읽히지 않거든요. 실제로 화면에 렌더링되는 것은 <img>이니까 alt 텍스트도 거기에 넣는 게 자연스럽습니다.

<picture>
  <source srcset="photo.webp" type="image/webp" />
  <img src="photo.jpg" alt="석양이 지는 서울 한강 풍경" />
</picture>

검색 엔진도 마찬가지입니다. 구글의 이미지 검색 봇은 <picture> 요소를 잘 이해하고, <img>alt 속성과 src 속성을 주로 참고합니다. 시멘틱 마크업의 관점에서 보면 <picture>는 이미지의 의미를 바꾸지 않고 기술적인 제공 방식만 달리하는 요소라서 시멘틱에 영향을 주지 않습니다.

한 가지 실수하기 쉬운 부분은 <img> 요소를 빼먹는 것인데요. <picture> 안에 <source>만 넣고 <img>를 빼버리면 아무것도 화면에 표시되지 않습니다. 유효하지 않은 마크업이 되는 것이니 반드시 <img>를 포함해야 합니다.

실전 활용 패턴

지금까지 살펴본 내용을 바탕으로 실제 프로젝트에서 자주 쓰이는 패턴 몇 가지를 정리해 보겠습니다.

첫 번째는 히어로 이미지 패턴입니다. 페이지 상단에 큰 배너 이미지를 넣을 때 가장 많이 쓰는 조합이죠.

<picture>
  <source
    media="(min-width: 1024px)"
    srcset="hero-desktop.avif"
    type="image/avif"
  />
  <source
    media="(min-width: 1024px)"
    srcset="hero-desktop.webp"
    type="image/webp"
  />
  <source media="(min-width: 1024px)" srcset="hero-desktop.jpg" />
  <source srcset="hero-mobile.avif" type="image/avif" />
  <source srcset="hero-mobile.webp" type="image/webp" />
  <img
    src="hero-mobile.jpg"
    alt="메인 배너"
    width="1200"
    height="600"
    loading="eager"
    fetchpriority="high"
  />
</picture>

히어로 이미지는 페이지에서 사용자가 가장 먼저 보는 요소이기 때문에 loading="eager"fetchpriority="high"를 넣어서 브라우저가 최우선으로 가져오게 합니다. 나머지 이미지에는 loading="lazy"를 쓰면 되고요.

두 번째는 로고 패턴입니다. 다크 모드에서 배경색이 바뀌면 로고가 안 보일 수 있으니까 모드에 따라 다른 로고를 보여주는 겁니다.

<picture>
  <source srcset="logo-dark.svg" media="(prefers-color-scheme: dark)" />
  <img src="logo-light.svg" alt="회사 로고" width="200" height="50" />
</picture>

prefers-color-scheme 미디어 쿼리를 사용하면 CSS 없이 HTML만으로 다크 모드 대응이 가능합니다.

마치며

<picture> 요소는 “상황에 맞는 이미지를 보여주자”는 단순한 아이디어를 HTML 차원에서 깔끔하게 구현한 것입니다. 아트 디렉션으로 화면에 맞는 구도를 보여주고, 포맷 분기로 최신 압축 기술의 혜택을 누리면서도 하위 호환성을 잃지 않을 수 있죠.

물론 단순히 같은 이미지를 여러 해상도로 제공하는 거라면 <img srcset>만으로 충분합니다. <picture> 요소는 정말 필요한 순간, 즉 구도를 바꾸거나 포맷을 분기해야 할 때 꺼내 쓰면 됩니다.

이미지를 CSS로 다루는 방법이 궁금하다면 CSS object-fit 속성을, 프레임워크 차원에서 이미지 최적화를 자동으로 처리하는 방법이 궁금하다면 Astro 이미지 최적화를 참고해 보세요. 더 자세한 스펙이 궁금한 분은 MDN의 picture 요소 문서에서 확인하실 수 있습니다.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord