브라우저 텍스트 미리보기의 크기를 정하려면 원하는 뷰포트 너비에서 레이아웃된 DOM 요소를 측정하세요. 한 줄 텍스트의 지표에는 Canvas를 사용하세요. 이 가이드에서는 Mac에서 두 방법을 비교하고 너비와 높이가 서로 다른 이유를 설명합니다.

실제 작업에서는 브라우저 뷰포트가 바뀔 때 가상의 카드 레이블에 얼마나 많은 공간이 필요한지 알아내야 합니다. 유용한 결과에는 뷰포트, 사용 가능한 텍스트 너비, 글꼴 설정, 측정한 상자를 함께 기록해야 합니다. 이러한 조건이 없는 너비 값은 재현하기 어렵습니다.

어떤 텍스트 너비와 높이를 측정해야 할까요?

브라우저 API는 서로 다른 여러 크기 측정 질문에 답합니다. 계산을 시작하기 전에 필요한 결정에 맞는 측정 방법을 선택하세요.

질문 측정 방법 나타내는 값
줄바꿈되지 않은 문자열 하나가 가로로 얼마나 진행하나요? Canvas measureText(text).width 텍스트 진행 너비
Canvas 글리프 모양의 경계는 어떻게 되나요? Canvas actualBoundingBox… 속성 정렬 기준점과 기준선에 대한 글리프 경계
카드가 차지하는 레이아웃 공간은 얼마나 되나요? 요소 getBoundingClientRect() 패딩과 테두리를 포함한 바운딩 테두리 상자
줄바꿈 후 텍스트 조각은 어디에 있나요? Range getClientRects() 선택된 텍스트에 해당하는 사각형
텍스트 조각을 둘러싸는 사각형은 무엇인가요? Range getBoundingClientRect() 텍스트 조각을 합친 바운딩 사각형

Canvas 진행 너비와 글리프 너비는 다를 수 있습니다. 기울임꼴 모양은 진행 너비 바깥으로 튀어나올 수 있습니다. 글리프 경계를 구하려면 actualBoundingBoxLeft + actualBoundingBoxRight로 너비를 계산하고, actualBoundingBoxAscent + actualBoundingBoxDescent로 높이를 계산하세요. 이 측정값은 CSS 픽셀 단위입니다. TextMetrics — MDN

요소 측정값에는 패딩과 테두리가 포함되지만, 텍스트만 선택한 Range는 글꼴 지표를 사용해 선택된 텍스트를 측정합니다. 어느 쪽도 실제로 그려진 픽셀의 정확한 사각형이라고 할 수 없습니다. W3C 사양은 가로 방향 Range 텍스트 사각형을 상승 높이, 하강 높이, 텍스트 진행 너비를 사용해 정의합니다. Element: getBoundingClientRect() method — MDN, CSSOM View Module — W3C

브라우저 크기 미리보기에서는 공간을 할당할 때 요소 상자를 사용하고, 텍스트 흐름을 살펴볼 때 Range 사각형을 사용하세요. 줄바꿈되지 않은 문자열을 확인하는 별도 기준으로 Canvas 측정값을 유지하세요.

먼저 뷰포트와 텍스트 조건을 설정하세요

Mac의 Chrome에서 DevTools를 열고 Toggle device toolbar를 활성화한 다음 Dimensions에서 Responsive를 선택하세요. 뷰포트 입력란에 원하는 너비와 높이를 입력합니다. Chrome에서는 숫자 입력과 드래그를 통한 뷰포트 크기 조정을 모두 지원합니다. 데스크톱 미리보기에서는 해당 컨트롤을 사용할 수 있을 때 데스크톱 기기 유형을 선택하세요. Simulate mobile devices with device mode — Chrome for Developers

뷰포트 너비와 텍스트 너비를 구분하세요. 카드에는 최대 너비, 바깥 여백, 패딩, 테두리가 있을 수 있습니다. 카드가 최대 너비에 도달하면 브라우저 너비를 더 늘려도 텍스트 영역은 더 이상 커지지 않습니다.

예를 들어 카드의 테두리 상자 너비가 예시로 360px이고, 양쪽 가로 패딩이 각각 16px, 테두리가 양쪽에 각각 1px 있다고 가정해 보세요. 사용 가능한 콘텐츠 너비는 360 − 32 − 2 = 326px입니다. 이것이 줄바꿈에 사용할 수 있는 너비입니다. 요소의 바운딩 사각형에는 여전히 360px이 표시됩니다. 이는 문서화된 테두리 상자 측정 방식에 따른 결과입니다. Element: getBoundingClientRect() method — MDN

의도한 글꼴 패밀리, 굵기, 크기, 스타일, 줄 높이, 자간, 줄바꿈 규칙을 맞추세요. 특히 white-space: normal은 공백을 하나로 합치고 줄바꿈을 허용하지만, pre-wrap은 줄바꿈을 허용하면서 공백과 원본의 줄바꿈을 보존합니다. 한 설정으로 만든 미리보기는 다른 설정을 신뢰성 있게 나타낼 수 없습니다. white-space — MDN

글꼴 크기와 줄 높이도 구분하세요. 가로 쓰기에서 line-height은 줄 상자 높이를 결정하는 데 영향을 줍니다. 글자의 실제 보이는 높이를 나타내지는 않습니다. 따라서 글꼴이 16px이고 줄 높이가 24px인 경우, Canvas 글리프 경계를 이용한 계산과 높이 계산 방식이 달라집니다. line-height — MDN

완전한 가상 텍스트 미리보기를 실행하세요

빈 탭을 열고 DevTools에서 Console을 선택한 뒤, 다음 브라우저 스크립트 전체를 한 번 실행하세요. 기본 카드와 측정 보고서가 생성됩니다. 라이브러리, 외부 글꼴, 기존 선택자, HTML 파일은 필요하지 않습니다. 다른 사본을 실행하기 전에 빈 탭을 새로고침하세요.

카드는 뷰포트에 맞춰 커지다가 테두리 상자 너비 480px에 도달하면 더 이상 커지지 않습니다. 창 또는 에뮬레이션된 뷰포트 크기를 조정하면 보고서가 업데이트됩니다. 이 예제는 균일한 스타일이 적용된 일반 가로 텍스트 블록만을 대상으로 합니다. 브라우저 종속 코드를 검토했지만, 준비 과정에서 브라우저에서 실행하지는 않았습니다.

(async () => {
  const text =
    "Sample collection: round forms, soft gradients, and simple patterns.";

  document.body.style.margin = "0";

  const card = document.createElement("div");
  card.textContent = text;
  card.lang = "en";
  card.dir = "ltr";
  card.style.cssText = [
    "box-sizing: border-box",
    "width: min(480px, calc(100% - 48px))",
    "margin: 24px",
    "padding: 16px",
    "border: 1px solid #777",
    "font: normal 400 16px/24px Arial, sans-serif",
    "font-kerning: normal",
    "letter-spacing: 0px",
    "word-spacing: 0px",
    "white-space: normal",
    "overflow-wrap: normal",
    "word-break: normal",
    "hyphens: none",
    "color: #222",
    "background: #f4f4f4"
  ].join(";");

  const report = document.createElement("pre");
  report.style.cssText =
    "margin: 24px; white-space: pre-wrap; overflow-wrap: anywhere;";
  document.body.append(card, report);

  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");
  if (!ctx) {
    report.textContent = "A Canvas 2D context is unavailable.";
    return;
  }

  // Establish layout before waiting for the fonts used by that layout.
  card.getBoundingClientRect();
  await document.fonts.ready;

  const format = value =>
    Number.isFinite(value) ? value.toFixed(2) : "unavailable";

  function measure() {
    const style = getComputedStyle(card);
    const box = card.getBoundingClientRect();
    const horizontalExtras =
      parseFloat(style.paddingLeft) + parseFloat(style.paddingRight) +
      parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth);

    const range = document.createRange();
    range.selectNodeContents(card);
    const textBox = range.getBoundingClientRect();
    const fragments = Array.from(range.getClientRects());

    ctx.font = "normal 400 16px Arial, sans-serif";
    ctx.fontKerning = "normal";
    ctx.direction = "ltr";
    ctx.textAlign = "left";
    ctx.textBaseline = "alphabetic";
    const metrics = ctx.measureText(text);
    const inkWidth =
      metrics.actualBoundingBoxLeft + metrics.actualBoundingBoxRight;
    const inkHeight =
      metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;

    report.textContent = [
      "All dimensions below are CSS pixels.",
      `Layout viewport: ${document.documentElement.clientWidth} x ${document.documentElement.clientHeight}`,
      `Card border box: ${format(box.width)} x ${format(box.height)}`,
      `Available text width: ${format(box.width - horizontalExtras)}`,
      `Range bounds: ${format(textBox.width)} x ${format(textBox.height)}`,
      `Range fragment count: ${fragments.length}`,
      `Canvas unwrapped advance: ${format(metrics.width)}`,
      `Canvas glyph bounds: ${format(inkWidth)} x ${format(inkHeight)}`,
      `Computed line height: ${style.lineHeight}`
    ].join("\n");
  }

  measure();
  window.addEventListener("resize", measure);
})().catch(error => console.error("Preview failed:", error));

요소와 Range 호출은 브라우저의 실제 레이아웃을 사용합니다. Canvas는 같은 가상 문자열을 줄바꿈되지 않은 한 덩어리로 측정합니다. HTML 표준에 따르면 Canvas 텍스트 준비 과정에서 ASCII 공백 문자는 공백으로 바뀝니다. measureText()에 줄바꿈 문자를 전달해도 여러 줄로 된 CSS 문단이 만들어지지는 않습니다. HTML Standard: The canvas element — WHATWG

스크립트는 레이아웃을 설정한 뒤 document.fonts.ready을 기다립니다. MDN에 따르면 이 프라미스는 사용된 글꼴의 로딩과 레이아웃 작업이 완료되면 이행됩니다. 선언된 모든 글꼴이 로드되었거나 원래 우선 지정한 글꼴이 반드시 표시된다는 뜻은 아닙니다. Document: fonts property — MDN

결과를 읽고 텍스트가 들어가는지 판단하세요

먼저 Available text width를 확인한 다음, 뷰포트 크기 두 개에서 카드 높이를 비교하세요. 줄바꿈으로 줄이 하나 추가되면 카드 높이는 달라지지만, Canvas의 줄바꿈되지 않은 진행 너비는 그대로일 수 있습니다. 이 조합을 보면 줄바꿈이 바뀐 것인지 글꼴이 바뀐 것인지 구분하는 데 도움이 됩니다.

이 예제의 다음 너비는 측정된 글꼴 결과가 아닌 예시 계산값입니다. 레이아웃 뷰포트의 너비가 표시된 값이고 스크립트 스타일이 변경되지 않았다고 가정합니다.

레이아웃 뷰포트 너비 카드 테두리 상자 너비 사용 가능한 텍스트 너비
360px 312px 278px
800px 480px 446px
1200px 480px 446px

마지막 두 행은 브라우저 너비를 늘려도 텍스트 줄바꿈이 바뀌지 않을 수 있는 이유를 보여줍니다. 카드가 이미 최대 너비에 도달했기 때문입니다. 뷰포트만 바꿔서는 카드의 콘텐츠 공간을 늘릴 수 없습니다.

균일한 텍스트만 들어 있는 간단한 블록에서 줄 높이가 24px이고 세 줄로 표시된다면, 예시로 줄 상자 공간은 72px입니다. 세로 패딩 32px와 테두리 2px를 더하면 예시 카드 높이는 106px입니다. 이 산술은 결과를 이해하는 데 유용하지만, 콘텐츠나 스타일이 복잡해지면 최종 판단에는 측정한 DOM 높이를 사용해야 합니다. line-height — MDN

Range fragment count를 보편적인 줄 수로 간주하지 마세요. Range API는 선택 규칙에 따라 텍스트와 요소의 사각형을 반환합니다. 서식 있는 텍스트, 중첩된 요소, 분할된 레이아웃에서는 사각형 집합이 더 복잡해질 수 있습니다. 샘플에서는 진단 과정을 이해하기 쉽도록 일반 텍스트 노드 하나만 선택합니다. CSSOM View Module — W3C

마찬가지로 ceil(unwrappedWidth / availableWidth)을 사용해 줄바꿈된 높이를 추정하지 마세요. 이 공식은 텍스트가 가로 방향의 어느 위치에서나 임의로 나뉠 수 있다고 가정합니다. 브라우저의 줄바꿈은 설정된 공백 및 줄 나눔 동작을 따라야 하므로, 줄바꿈된 블록을 직접 측정하세요. white-space — MDN

측정값이 서로 다르면 무엇을 확인해야 할까요?

먼저 측정 경계를 확인하세요. 너비가 더 큰 카드 사각형과 너비가 더 좁은 Range 사각형이 모두 올바를 수 있습니다. 카드는 레이아웃 공간을 확보하고 Range는 선택된 텍스트를 나타냅니다. Canvas 글리프 높이는 또 다른 목적을 위한 세 번째 값입니다.

그다음 글꼴이 준비되었는지 확인하세요. 웹 글꼴을 선언하는 경우 측정 전에 일치하는 글꼴 페이스를 명시적으로 요청하려면 document.fonts.load(fontSpecification, sampleText)를 사용할 수 있습니다. 이 요청이 거부될 때는 성공한 것처럼 조용히 대체하지 말고 거부를 처리하세요. 선택적 텍스트 인수는 유니코드 범위에 따라 필터링하지만, 개별 글리프의 지원 여부를 확인하지는 않습니다. FontFaceSet: load() method — MDN

레이블 너비를 계산할 때 셰이핑 경계를 보존하세요. 문자별 너비를 따로 측정해 더하지 말고 문자열 전체를 측정하세요. Canvas 사양은 현재 글꼴과 텍스트 설정을 사용해 배치된 글리프 런을 준비합니다. 그 결과가 해당 문자열을 측정하는 적절한 단위입니다. 이 샘플의 Canvas 비교는 명시된 글꼴 설정으로 한정하고, 모든 CSS 타이포그래피 기능을 재현한다고 가정하지 마세요. HTML Standard: The canvas element — WHATWG

기존 페이지에서는 실제 컴포넌트를 그 자리에서 측정하세요. 텍스트를 일반적인 숨김 상자로 옮기면 관련 스타일과 상위 컨텍스트도 재현하지 않는 한 레이아웃 조건이 달라집니다. 연결된 레이아웃 상자가 없는 요소는 유용한 일반 상자 측정값을 제공하지 못하며, CSS 변환은 이 API들이 보고하는 사각형에 영향을 줍니다. 가상 예제에서는 두 가지 복잡성을 모두 피합니다. CSSOM View Module — W3C

여러 브라우저 크기에서 미리보기를 반복하려면 어떻게 해야 할까요?

가끔 확인하는 경우에는 각 뷰포트 크기를 직접 입력하고 보고서를 기록하세요. 아주 좁은 레이아웃과 아주 넓은 레이아웃만 확인하지 말고 줄바꿈이 바뀌는 지점 주변의 너비를 비교하세요.

반복 확인에는 Playwright에서 뷰포트 크기를 명시적으로 지정하고 별도의 deviceScaleFactor 설정을 사용할 수 있습니다. 설명서에서는 기기 프리셋의 뷰포트를 재정의하는 방법도 안내합니다. 이를 통해 지원되는 자동화 절차를 만들 수 있습니다. 뷰포트를 설정하고 페이지를 불러온 다음 글꼴과 관련 콘텐츠가 준비될 때까지 기다린 후 브라우저에서 DOM 측정값을 수집합니다. Emulation — Playwright

CSS 크기와 디스플레이 픽셀 밀도를 별도로 관리하세요. Playwright는 뷰포트와 배율을 별도의 설정으로 제공하며, Canvas 텍스트 지표는 CSS 픽셀로 표시됩니다. 디스플레이 밀도가 높다고 해서 레이블에 할당할 CSS 너비를 두 배로 늘릴 필요는 없습니다. Emulation — Playwright, TextMetrics — MDN

따라서 유용한 미리보기 결과는 조건을 간단히 기록한 것입니다. 브라우저, 뷰포트, 텍스트, 타이포그래피, 콘텐츠 너비, 측정된 카드 높이를 기록하세요. 줄바꿈된 컴포넌트의 크기를 정할 때는 DOM 결과를 사용하고, 단일 문자열에 관한 질문에는 Canvas 지표를 유지하며, 렌더링을 재현해야 하는 각 브라우저에서 측정을 반복하세요.

출처