반응형 이미지 후보를 예측하려면 sizes를 CSS 너비로 해석하고, 여기에 기기 픽셀 비율을 곱한 다음 그 목표값을 srcset과 비교합니다. 그런 다음 선택된 URL을 확인하세요. 최종 선택은 브라우저의 재량에 달려 있습니다.

이 안내는 Mac의 Chrome에서 직접 작성한 웹 페이지를 확인하는 사람을 위한 것입니다. 가능한 후보를 계산하고, 추상 도형이 담긴 로컬 페이지를 테스트한 다음, 잘못된 크기 선언과 정상적인 브라우저 선택 동작을 구분합니다.

sizes, srcset, DPR은 각각 브라우저에 무엇을 알려 주나요?

너비 디스크립터를 사용할 때 srcset는 사용 가능한 리소스와 각 리소스의 고유 픽셀 너비를 나열합니다. 800w 같은 디스크립터는 실제 너비가 800픽셀인 이미지를 가리켜야 합니다. 너비 디스크립터와 2x 같은 밀도 디스크립터는 별도의 소스 세트에 두세요. 하나의 srcset에 섞어 쓰면 유효하지 않습니다. MDN의 이미지 요소 참조 문서에서 이러한 요구사항을 설명합니다.

sizes는 리소스 선택에 사용할 표시 너비를 제공합니다. 브라우저는 조건을 순서대로 평가하고 처음 일치하는 항목을 사용합니다. 무조건 적용되는 기본값은 마지막에 두세요. 소스 크기에는 480px, 50vw, calc(100vw - 32px) 같은 길이 값을 사용할 수 있습니다. 50% 같은 백분율 값은 유효한 슬롯 너비가 아닙니다. HTML에서 반응형 이미지 사용하기

표시 크기는 CSS가 제어합니다. CSS와 명시적인 sizes 목록을 서로 일치시켜야 하는 두 가지 설명으로 보세요. 하나는 레이아웃을 결정하고, 다른 하나는 리소스 선택에 필요한 정보를 제공합니다. sizes만 바꾸면 해당 레이아웃이 구현되지는 않습니다. HTML 표준: Sizes 속성

DPR은 물리 픽셀과 CSS 픽셀의 관계를 나타냅니다. Mac 모델이나 디스플레이 이름으로 값을 추정하지 말고 window.devicePixelRatio을 확인하세요. 페이지 확대/축소에 따라 이 값이 바뀔 수 있고, 창을 다른 디스플레이로 옮겨도 달라질 수 있습니다. 핀치 확대/축소는 DPR을 바꾸지 않습니다. MDN: Window.devicePixelRatio

가능한 후보는 어떻게 계산하나요?

해석된 소스 크기 S, 리소스 너비 W, DPR D에 대해 다음 두 가지 동등한 계산식을 사용하세요.

목표 리소스 너비 = S × D
후보 유효 밀도 = W ÷ S

HTML 처리 모델은 각 후보 너비를 소스 크기로 나누어 너비 디스크립터를 정규화합니다. MDN은 크기 힌트에 DPR을 곱하는 방법으로 실용적인 예측을 설명합니다. HTML 표준: 소스 밀도 정규화, MDN: HTMLImageElement.sizes

유용한 실무 추정치로는 목표 너비 이상인 후보 중 가장 작은 것을 선택하세요. 모든 후보가 목표보다 작다면 사용 가능한 후보 중 가장 큰 것을 찾고 부족한 정도를 기록하세요. 아래 연습에서는 이 명시적인 예측 규칙을 사용합니다. HTML 표준은 최종 선택을 구현 방식에 맡기며, 모든 브라우저가 이 반올림 규칙을 따라야 한다고 요구하지 않습니다. HTML 표준: 이미지 소스 선택

다음 소스 크기 목록을 480w, 800w, 1200w, 1440w 후보와 함께 살펴보겠습니다.

(max-width: 600px) calc(100vw - 32px),
(max-width: 1200px) calc(50vw - 32px),
568px

100vw는 뷰포트 너비를 나타내고, 50vw는 그 너비의 절반을 나타냅니다. 이 단위는 이미지의 부모 컨테이너가 아니라 뷰포트를 기준으로 합니다. CSS 값 및 단위: 뷰포트 백분율 길이

다음 표에는 예시 계산이 담겨 있으며, 실제 브라우저 결과를 기록한 표는 아닙니다. 마지막 열에는 앞서 명시한 예측 규칙을 적용했습니다.

뷰포트 너비, CSS px DPR 해석된 소스 크기, CSS px 목표 리소스 너비, px 예측 후보
600 1 568 568 800w
600 2 568 1,136 1200w
601 2 268.5 537 800w
1,000 1 468 468 480w
1,000 2 468 936 1200w
1,400 2 568 1,136 1200w
1,400 3 568 1,704 목표에 미달하는 1440w

CSS 픽셀 기준 600에서 601로 바뀌는 구간은 테스트하기에 특히 유용합니다. 600에서는 첫 번째 조건이 뷰포트 너비에 가까운 슬롯을 제공합니다. 601에서는 두 번째 조건에 따라 훨씬 좁은 슬롯이 제공됩니다. 따라서 뷰포트가 더 넓어져도 목표 이미지 크기는 더 작아집니다. 이는 직접 작성한 이 레이아웃 규칙에서 그대로 도출됩니다.

뷰포트가 1,000픽셀이고 DPR이 2일 때 소스 크기는 CSS 픽셀 기준 468입니다. 네 후보의 유효 밀도는 각각 대략 1.03x, 1.71x, 2.56x, 3.08x입니다. 예측 규칙을 적용하면 1200w 후보가 목표 밀도 2x를 처음으로 넘습니다.

로컬 테스트를 완성하려면 어떻게 해야 하나요?

Python 3.8 이상이 설치된 Mac에서 Chrome을 사용하세요. 다음 전체 프로그램을 일반 텍스트 편집기에서 responsive_demo.py로 저장합니다. 이 프로그램은 같은 사각형과 원이 담긴 PNG 파일 네 개를 만들고, 이에 맞는 페이지를 작성한 다음 로컬 서버를 시작합니다. 생성된 파일은 모두 새 임시 디렉터리에 저장되므로 기존 파일을 덮어쓰지 않습니다.

이 페이지는 의도적으로 지연 로딩하지 않는 이미지와 명시적인 소스 크기를 사용합니다. CSS 너비는 계산 표의 세 가지 분기와 일치합니다. 서버는 Python에 문서화된 ThreadingHTTPServer와 디렉터리 기반 요청 핸들러를 사용합니다. Python: http.server

from functools import partial
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
import struct
import tempfile
import zlib


def chunk(kind, data):
    return (
        struct.pack("!I", len(data)) + kind + data
        + struct.pack("!I", zlib.crc32(kind + data))
    )


def make_png(width):
    height = width // 2
    rows = []
    for y in range(height):
        row = bytearray()
        for x in range(width):
            color = (242, 244, 248)
            if width // 8 <= x < width // 2 and height // 4 <= y < 3 * height // 4:
                color = (70, 100, 160)
            if (x - 3 * width // 4) ** 2 + (y - height // 2) ** 2 < (height // 5) ** 2:
                color = (220, 160, 60)
            row.extend(color)
        rows.append(b"\x00" + row)
    header = struct.pack("!IIBBBBB", width, height, 8, 2, 0, 0, 0)
    return (
        b"\x89PNG\r\n\x1a\n"
        + chunk(b"IHDR", header)
        + chunk(b"IDAT", zlib.compress(b"".join(rows)))
        + chunk(b"IEND", b"")
    )


page = '''<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Shape A candidate test</title>
<style>
  body { margin: 0; padding: 16px; font-family: sans-serif; }
  #shape { display: block; width: calc(100vw - 32px); height: auto; }
  @media (min-width: 600.01px) {
    #shape { width: calc(50vw - 32px); }
  }
  @media (min-width: 1200.01px) {
    #shape { width: 568px; }
  }
</style>
<p>Shape A: rectangle and circle</p>
<img id="shape" src="shape-a-800.png" width="1440" height="720"
     alt="Blue rectangle and amber circle on a pale background"
     srcset="shape-a-480.png 480w, shape-a-800.png 800w,
             shape-a-1200.png 1200w, shape-a-1440.png 1440w"
     sizes="(max-width: 600px) calc(100vw - 32px),
            (max-width: 1200px) calc(50vw - 32px), 568px">
</html>
'''

root = Path(tempfile.mkdtemp(prefix="responsive-shapes-"))
for width in (480, 800, 1200, 1440):
    (root / f"shape-a-{width}.png").write_bytes(make_png(width))
(root / "index.html").write_text(page, encoding="utf-8")
handler = partial(SimpleHTTPRequestHandler, directory=str(root))
with ThreadingHTTPServer(("127.0.0.1", 0), handler) as server:
    print(f"Files: {root}", flush=True)
    print(f"Open http://127.0.0.1:{server.server_port}/", flush=True)
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass

터미널에서 프로그램을 저장한 폴더로 이동한 다음 실행합니다.

python3 responsive_demo.py

프로그램이 출력한 로컬 주소를 Chrome에서 엽니다. 테스트하는 동안 터미널을 계속 실행해 두고, 마치면 터미널에서 Control+C를 누르세요. 프로그램이 사용 가능한 포트를 요청하므로 고정된 포트 번호를 가정하지 말고 출력된 주소를 사용하세요.

CSS 임계값은 데스크톱 테스트의 정수 뷰포트 너비에 맞춰 600.01px과 1200.01px로 설정되어 있습니다. 표에 나온 정수 값을 정확히 테스트하세요. 이 예시를 실제 서비스 레이아웃에 적용할 때는 소수 너비 동작을 포함해 실제 CSS 중단점과 소스 크기 조건을 일치시키세요.

Mac의 Chrome에서 선택 결과를 어떻게 확인하나요?

도형을 마우스 오른쪽 버튼으로 클릭하고 Inspect를 선택해 이미지 요소가 선택된 DevTools를 엽니다. Chrome 문서에서는 이 방법으로 요소와 적용된 스타일을 직접 확인할 수 있다고 설명합니다. Chrome DevTools 열기

  1. Toggle device toolbar를 클릭하고 Dimensions에서 Responsive를 선택합니다.
  2. 뷰포트 너비를 1000으로 설정합니다.
  3. 툴바의 More options 메뉴에서 Add device pixel ratio를 선택한 다음 DPR 2를 선택합니다.
  4. 필요한 경우 More options를 열고 Add device type을 선택한 다음 Desktop을 선택합니다.
  5. Network를 열고 Disable cache를 활성화한 다음 DevTools를 연 상태로 페이지를 새로고침합니다.
  6. shape-a로 필터링해 요청된 이미지를 찾습니다.

Chrome 문서에서 Device Mode 안내서를 통해 뷰포트, DPR, 기기 유형 제어 방법을, Network 기능 참조 문서를 통해 캐시 제어 방법을 설명합니다.

페이지가 로드된 뒤 DevTools Console에서 다음 진단 코드를 전체 실행합니다.

(() => {
  const image = document.getElementById("shape");
  if (!image) throw new Error("The Shape A image was not found.");

  const viewport = window.innerWidth;
  const slot = window.matchMedia("(max-width: 600px)").matches
    ? viewport - 32
    : window.matchMedia("(max-width: 1200px)").matches
      ? viewport / 2 - 32
      : 568;

  console.table({
    viewportCSSPixels: viewport,
    devicePixelRatio: window.devicePixelRatio,
    declaredSlotCSSPixels: slot,
    renderedWidthCSSPixels: image.getBoundingClientRect().width,
    targetResourcePixels: slot * window.devicePixelRatio,
    selectedURL: image.currentSrc,
    naturalWidthCSSPixels: image.naturalWidth
  });
})();

이 코드는 이 데모에 한해 선언된 슬롯을 계산합니다. 일반적인 sizes 파서는 아닙니다. 선언된 슬롯을 렌더링된 너비와 비교한 뒤, 선택된 파일 이름을 예측 표와 비교하세요.

currentSrc는 브라우저가 선택한 전체 URL을 반환합니다. 리소스가 성공적으로 로드되었음을 확인해 주는 것은 아니므로 이미지 요청과 화면에 표시된 결과도 확인하세요. 이 예시에서 src을 읽으면 대체 속성만 확인할 수 있습니다. MDN: HTMLImageElement.currentSrc

naturalWidth을 인코딩된 PNG의 너비로 해석하지 마세요. 이 값은 CSS 픽셀 기준으로 밀도 보정된 너비입니다. 이처럼 통제된 테스트에서는 생성된 파일의 크기와 파일 이름으로 리소스 너비를 확인할 수 있습니다. MDN: HTMLImageElement.naturalWidth

각 조건에 대해 600과 601 CSS 픽셀에서 테스트하고 매번 새로고침하세요. 뷰포트, 보고된 DPR, 슬롯, 선택된 파일 이름, 요청 완료 여부를 기록합니다. 이 기록을 사용하면 일관된 새로고침 결과와 이미 로드된 페이지의 크기를 조절하면서 이어진 선택 과정을 구분할 수 있습니다.

예측이 빗나가면 무엇을 확인해야 하나요?

후보 목록을 바꾸기 전에 입력값을 차례로 확인하세요.

  1. 디스크립터 유형을 확인합니다. 1x, 2x 같은 밀도 디스크립터를 사용할 때는 sizes을 통한 너비 디스크립터 계산을 적용하지 마세요.
  2. 처음 일치하는 조건을 확인합니다. 먼저 일치하는 조건이 뒤에 나오는 항목보다 우선합니다.
  3. 선언된 너비와 렌더링된 너비를 비교합니다. 100vw 선언은 CSS가 좁은 열로 제한하는 슬롯의 너비를 실제보다 크게 나타낼 수 있습니다.
  4. 실제 DPR을 확인합니다. 페이지 확대/축소를 하거나 디스플레이를 바꾼 뒤 다시 확인하세요.
  5. 조건을 통제해 새로고침한 결과를 비교합니다. 테스트를 반복할 때 뷰포트, DPR, 로딩 조건을 동일하게 유지하세요.
  6. 리소스를 사용할 수 있는지 확인합니다. URL이 선택된 것과 이미지가 성공적으로 표시된 것은 서로 다른 관찰 결과입니다.

명시적인 소스 크기가 없거나 사용할 수 없으면 HTML 파서가 기본값 100vw를 적용할 수 있습니다. 이 때문에 좁은 이미지에 비해 목표값이 예상보다 커질 수 있습니다. 브라우저가 CSS에서 열 너비를 추론할 것으로 가정하지 말고 정확한 소스 크기 목록을 제공하세요. HTML 표준: sizes 속성 파싱

페이지가 picture 요소를 사용한다면 밀도를 계산하기 전에 어떤 소스 분기가 적용되는지 확인하세요. 그 분기의 후보 중 하나를 고르기 전에 소스가 선택됩니다. 서로 다른 자르기 이미지는 이 아트 디렉션 메커니즘에 속합니다. HTML에서 반응형 이미지 사용하기

이 방법에 영향을 주는 지원 제한은 무엇인가요?

MDN은 sizes 속성이 여러 브라우저에서 널리 지원된다고 표시하지만, 그렇다고 최신 키워드가 모두 똑같이 지원되는 것은 아닙니다. 여기서 사용한 명시적 조건은 자동 크기 조정에 의존하지 않고도 간단히 테스트할 수 있게 해 줍니다. MDN: HTMLImageElement.sizes

sizes="auto"은 지연 로드 이미지에 유효하며, 선택 과정에서 해당 이미지의 레이아웃 너비를 사용할 수 있게 합니다. 이 키워드를 사용한다면 이를 지원하지 않는 브라우저를 위해 auto 뒤에 명시적인 대체 크기를 두고 이미지 크기도 지정하세요. 이러한 전제 조건을 바꾸지 않고 이 키워드를 이 예시처럼 지연 로딩하지 않는 이미지에 적용하지 마세요. MDN의 이미지 요소 참조 문서

Chrome의 기기 제어 기능은 에뮬레이션된 조건에서 Chrome의 동작을 테스트합니다. Safari나 Firefox가 어떤 후보를 선택하는지 보여 주지는 않습니다. Chrome은 또한 Device Mode가 근사치라고 설명하므로 다른 브라우저의 동작이 중요하다면 해당 브라우저에서 직접 테스트하세요. Chrome DevTools: Device Mode의 제한 사항

유용한 예측의 마지막에는 확인할 수 있는 기록이 남습니다. 여기에는 적용되는 소스 세트, 해석된 소스 크기, 보고된 DPR, 계산된 목표값, 관찰된 currentSrc가 포함됩니다. 이 입력값이 작성한 레이아웃과 일치하면 리소스 선택을 명확하게 설명하고 다음 변경이 CSS, sizes, 사용 가능한 이미지 너비 중 어디에 필요한지 파악할 수 있습니다.

출처