SVG use 인스턴스 주변의 예상치 못한 공백을 없애려면 원인이 소스 viewBox, 종횡비 맞춤 또는 주변 CSS 줄 상자 중 어디에 있는지 확인하세요. 이 가이드에서는 배치 좌표를 예측 가능하게 유지하면서 각 원인을 진단하고 수정하는 방법을 보여줍니다.

작업은 간단한 데스크톱 웹페이지 편집입니다. SVG 안에 재사용 가능한 도형을 정확히 배치한 다음, 주변 HTML 레이아웃이 의도한 대로 맞도록 합니다. Mac의 일반 텍스트 편집기에서 예제를 따라 하고, 저장한 HTML 파일을 최신 데스크톱 브라우저에서 열면 됩니다.

공백을 제어하는 좌표계는 무엇인가요?

확인할 경계는 세 가지입니다. 바깥쪽 SVG 요소의 CSS 상자, 참조된 심볼에 사용되는 뷰포트, 그리고 그 심볼 안의 실제 도형입니다. W3C는 SVG 뷰포트와 브라우저의 CSS 뷰포트를 명확히 구분합니다. viewBox는 SVG 사용자 좌표를 SVG 뷰포트에 매핑하며, 브라우저 창을 나타내지 않습니다. 좌표계, 변환 및 단위 — SVG 2

예를 들어 x="20"는 use 요소에서 SVG 좌표계의 오프셋을 지정합니다. 브라우저 창의 왼쪽 가장자리에서 CSS 픽셀 20만큼 떨어진 곳을 뜻하지는 않습니다. 배율 조정과 상위 요소의 변환에 따라 페이지에서 해당 오프셋이 표시되는 위치가 달라질 수 있습니다. SVG 명세에서는 use 위치 지정이 추가 변환이며 CSS 상자 레이아웃과 동작이 다르다고 설명합니다. 문서 구조 — SVG 2

먼저 다음 의사결정 표를 참고하세요. 서로 바꿔 쓸 수 있는 수정 방법을 나열한 것이 아니라, 문서화된 레이아웃 메커니즘에 따라 원인을 진단하는 순서입니다.

관찰되는 현상 먼저 확인할 항목 가능성 높은 수정 방법
SVG 전체 아래에 좁은 띠가 보임 인라인 서식과 상위 요소의 줄 높이 단독 SVG를 블록 요소로 만들거나 인라인 정렬을 조정합니다
반복되는 그래픽 내부에 빈 공간이 있음 도형 좌표와 심볼의 viewBox 비교 여백이 의도된 것이 아니라면 소스 경계를 좁힙니다
심볼 인스턴스의 양쪽에 여분의 공간이 있음 인스턴스 크기와 viewBox 비율 비교 비율을 일치시키거나 의도한 맞춤 정책을 선택합니다
로컬 배치는 맞지만 페이지 위치가 틀림 SVG 사용자 단위와 브라우저 뷰포트 좌표 비교 x 및 y를 지정하기 전에 좌표를 변환합니다
인스턴스 크기를 바꿔도 아무 변화가 없음 href가 지정하는 요소 맞춤을 위해 뷰포트를 정의하는 심볼을 사용하거나 도형을 변환합니다

완전한 로컬 예제로 공백 재현하기

다음 전체 문서를 일반 텍스트 편집기에서 svg-spacing.html로 저장한 뒤 브라우저에서 여세요. 합성 라벨과 사각형만 사용하며 라이브러리나 외부 파일은 없습니다. 연한 사각형은 의도한 인스턴스 뷰포트를, 더 진한 사각형은 재사용되는 그래픽을 표시합니다.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>SVG spacing example</title>
  <style>
    body {
      margin: 24px;
      font: 16px system-ui, sans-serif;
    }
    #canvas {
      display: block;
      width: 320px;
      height: 200px;
      outline: 1px solid #687580;
      background: #fff;
    }
    .viewport-guide { fill: #e4edf2; }
    .artwork { fill: #356b8c; }
    text { fill: #253744; font-size: 14px; }
  </style>
</head>
<body>
  <p>Two instances with different source bounds and fitting dimensions.</p>
  <svg id="canvas" xmlns="http://www.w3.org/2000/svg"
       width="320" height="200" viewBox="0 0 320 200"
       role="img" aria-labelledby="drawing-title">
    <title id="drawing-title">Padded and tightly fitted rectangles</title>
    <defs>
      <symbol id="padded" viewBox="0 0 100 100">
        <rect id="source-shape" class="artwork"
              x="20" y="20" width="60" height="60" />
      </symbol>
      <symbol id="tight" viewBox="20 20 60 60">
        <rect class="artwork"
              x="20" y="20" width="60" height="60" />
      </symbol>
    </defs>

    <rect class="viewport-guide"
          x="20" y="20" width="120" height="60" />
    <use href="#padded" x="20" y="20" width="120" height="60" />
    <text x="160" y="55">Padded source</text>

    <rect class="viewport-guide"
          x="20" y="110" width="60" height="60" />
    <use id="tight-instance" href="#tight"
         x="20" y="110" width="60" height="60" />
    <text x="110" y="145">Tight source</text>
  </svg>
</body>
</html>

두 인스턴스 모두 크기를 명시적으로 지정합니다. 따라서 공백을 조사하는 동안 암묵적 크기 지정에 의존하지 않습니다. MDN에 따르면 use는 참조된 SVG 콘텐츠를 재사용하며, 대상이 SVG이거나 viewBox가 있는 심볼인 경우 width 및 height가 맞춤에 영향을 줍니다. 이 예제에서는 현재의 href 속성을 사용합니다. MDN: use

첫 번째 사각형이 실제로 시작하는 위치 계산하기

첫 번째 소스의 viewBox는 100×100이지만 사각형은 각 방향에서 좌표 20부터 80까지만 차지합니다. 요청된 인스턴스 뷰포트는 120×60입니다. 기본 xMidYMid meet 맞춤을 사용하면 배율은 120 / 100과 60 / 100 중 더 작은 값인 0.6입니다. 배율이 적용된 viewBox는 60×60을 차지하므로 양쪽에 각각 30단위의 수평 여백이 생깁니다. 이는 W3C의 뷰포트 변환 알고리즘에 따른 것입니다. SVG 2: SVG 뷰포트의 등가 변환 계산

이 예시 입력값에서 채워진 사각형은 가로 방향으로 20 + 30 + 20 × 0.6 = 62, 세로 방향으로 20 + 20 × 0.6 = 32에서 시작합니다. 크기는 각 방향에서 60 × 0.6 = 36입니다. 산술 결과는 간단한 결정적 계산으로 확인했으며, 브라우저 테스트에서 측정한 값이 아니라 계산된 값입니다.

두 번째 인스턴스는 서로 독립적인 입력 두 가지를 바꿉니다. 소스 경계는 20 20 60 60이 되고 인스턴스 뷰포트는 60×60이 됩니다. 따라서 사각형이 뷰포트를 채우며 SVG 좌표 (20, 110)에서 시작합니다. 원인을 직접 분리해 보려면 한 번에 입력 하나만 바꾸세요.

인스턴스 내부의 공백을 어떻게 없애나요?

소스 경계에 의도치 않은 공백이 있다면 수정하기

viewBox에는 최소 x, 최소 y, 너비, 높이의 네 숫자가 들어갑니다. 원점은 반드시 0일 필요가 없습니다. 예제에서 viewBox="20 20 60 60"은 도형을 다시 작성하지 않고 기존 경계를 나타냅니다. MDN: viewBox

실제 그래픽의 경계를 좁히기 전에 내부 여백이 의도된 것인지 판단하세요. 재사용 가능한 도형 세트는 시각적 크기와 위치의 일관성을 유지하기 위해 공통 좌표계를 공유할 수 있습니다. 각 심볼의 경계를 따로 좁히면 이러한 관계가 달라질 수 있습니다. 그 일관성이 디자인의 일부라면 공통 경계를 유지하세요.

도형 경계를 확인할 때 getBBox()은 SVG 사용자 좌표계의 경계를 보고합니다. 기본적으로 획과 마커는 제외하며, 대상 요소 또는 상위 요소에 적용된 변환도 반영하지 않습니다. 따라서 채우기만 고려한 경계 상자로는 획이 있거나 변환된 그래픽을 자를 수 있다는 충분한 근거가 되지 않습니다. MDN: SVGGraphicsElement getBBox()

종횡비가 맞지 않을 때의 맞춤 방식 선택하기

경계가 딱 맞는 그래픽도 정사각형 소스를 가로로 넓은 뷰포트에 맞추면 주변에 공백이 생길 수 있습니다. 원하는 결과에 따라 정책을 선택하세요. 심볼 내부 맞춤을 위해서는 참조된 심볼에 preserveAspectRatio을 적용하고, 바깥 좌표 매핑을 진단할 때는 바깥쪽 SVG에 적용합니다. MDN: preserveAspectRatio

선택 결과 및 조건
소스와 뷰포트의 비율 일치 비율을 유지하면서 종횡비 맞춤으로 인한 공백을 방지합니다
xMidYMid meet 전체 viewBox를 중앙에 표시합니다. 비율이 다르면 공백이 생깁니다
xMinYMin meet 맞춤된 viewBox를 왼쪽 위로 이동합니다. 남는 공백은 그대로 있습니다
none 비균일 배율 조정을 허용하므로 정사각형이 직사각형이 될 수 있습니다
xMidYMid slice 뷰포트를 덮지만 소스 콘텐츠 일부가 뷰포트 밖으로 나갑니다. 표시 여부는 클리핑에 따라 결정됩니다

정렬을 바꾸면 맞춤으로 생긴 공간의 위치가 달라질 뿐, 소스 도형에 이미 들어 있는 여백이 제거되지는 않습니다. 마찬가지로 모든 소스 콘텐츠를 뷰포트 안에 유지해야 할 때는 slice을 사용하면 안 됩니다. 이러한 구분은 MDN의 정렬, meet, slice 및 비균일 맞춤에 대한 정의를 따릅니다. MDN: preserveAspectRatio

크기가 적용되지 않는 것 같으면 참조 대상을 확인하기

일반 그룹이나 경로를 참조하는 use 요소만으로는 해당 요소에 맞춤용 뷰포트가 생기지 않습니다. W3C는 참조된 요소가 새 뷰포트를 만들지 않을 때 인스턴스의 width 및 height이 아무 효과가 없다고 설명합니다. 인스턴스별로 예측 가능한 맞춤을 원한다면 위 예제처럼 재사용할 도형을 경계가 명시된 심볼에 넣으세요. 도형을 직접 배율 조정하려면 변환을 사용하고 배치에 미치는 영향도 고려하세요. SVG 2: 재사용 그래픽의 레이아웃

바깥쪽 SVG 아래의 띠를 어떻게 없애나요?

SVG 바깥의 공백은 HTML 인라인 서식에서 생길 수 있습니다. WHATWG는 SVG가 구문 콘텐츠 안에 들어갈 수 있도록 허용하므로 주변 페이지가 SVG를 텍스트와 나란히 배치할 수 있습니다. CSS 줄 상자에는 기준선 위아래의 글꼴 메트릭이 포함되므로 인라인 그래픽 상자 아래에 눈에 보이는 공간이 남을 수 있습니다. HTML 표준: SVG, CSS 2: 줄 높이 계산

전체 예제에서는 이미 #canvas에 display: block을 설정해 SVG가 인라인 줄 상자에 참여하지 않도록 합니다. 단독 그림이라면 이 규칙을 유지하세요. 이 메커니즘을 살펴보려면 잠시 display: inline으로 바꾸고 주변 줄을 확인하세요.

SVG를 텍스트 옆에 두어야 한다면 바깥쪽 SVG에 vertical-align: bottom을 적용하고 텍스트 정렬 결과를 확인하세요. 이 속성은 인라인 수준 상자를 해당 줄 안에서 정렬하며 심볼 경계나 내부 맞춤을 바꾸지는 않습니다. 다른 콘텐츠와 상위 요소의 줄 높이가 줄의 전체 높이를 결정할 수도 있습니다. MDN: vertical-align

다음 CSS 선언은 바깥쪽 SVG에 적용할 수 있는 대안입니다. 각 인스턴스에 모두 붙여 넣는 누적 수정 방법은 아닙니다.

/* Standalone drawing */
#canvas {
  display: block;
}

/* Alternative when the drawing must stay inline */
/*
#canvas {
  display: inline;
  vertical-align: bottom;
}
*/

좌표가 브라우저 뷰포트에서 온 값이라면 어떻게 하나요?

getBoundingClientRect()은 브라우저 뷰포트 기준 CSS 픽셀로 요소의 위치를 반환합니다. 페이지를 스크롤하면 위치가 바뀝니다. 이 값은 SVG 사용자 좌표와 다른 공간에 속하므로 left 및 top을 인스턴스의 x 및 y에 그대로 복사하는 것은 대개 올바르지 않습니다. MDN: Element getBoundingClientRect()

입력이 브라우저 뷰포트의 한 점이라면 좌표 변환을 사용하세요. getScreenCTM()은 SVG 변환 행렬을 제공하며, 역행렬을 적용하면 해당 점을 선택한 요소의 로컬 좌표로 되돌릴 수 있습니다. 점의 matrixTransform() 메서드로 변환할 수 있습니다. MDN: getScreenCTM(), MDN: matrixTransform()

다음 콘솔 코드는 위 전체 문서에 선택적으로 추가할 수 있습니다. 브라우저 뷰포트 점 (100, 150)을 바깥쪽 SVG의 사용자 좌표로 변환한 뒤, 경계가 딱 맞는 인스턴스를 해당 위치로 이동합니다. 변환되지 않은 인스턴스가 해당 SVG의 직계 자식으로 남아 있다고 가정합니다.

(() => {
  const canvas = document.getElementById("canvas");
  const instance = document.getElementById("tight-instance");
  if (!canvas || !instance) {
    throw new Error("Open the complete SVG spacing example first.");
  }

  const matrix = canvas.getScreenCTM();
  if (!matrix) {
    throw new Error("The SVG coordinate matrix is unavailable.");
  }

  const viewportPoint = new DOMPoint(100, 150);
  const localPoint = viewportPoint.matrixTransform(matrix.inverse());
  if (!Number.isFinite(localPoint.x) || !Number.isFinite(localPoint.y)) {
    throw new Error("The SVG coordinate matrix cannot be inverted.");
  }

  instance.setAttribute("x", String(localPoint.x));
  instance.setAttribute("y", String(localPoint.y));
})();

역행렬이 존재하지 않으면 NaN 구성 요소가 나오므로 유한수 확인이 중요합니다. 레이아웃이나 스크롤이 바뀐 뒤 새 점을 변환할 때는 행렬을 다시 계산하세요. 인스턴스가 변환된 그룹 안에 있다면 해당 상위 그룹의 좌표계로 변환하세요. 이 코드는 조사 과정에서 API 문서와 대조해 검토했으며 브라우저에서 실행하지는 않았습니다. MDN: DOMMatrixReadOnly inverse()

마지막으로 세 경계를 따로 확인하세요. 바깥쪽 SVG 상자, 맞춤된 심볼 뷰포트, 그래픽 자체의 경계입니다. 원치 않는 공백을 만드는 경계를 수정한 뒤 의도한 페이지 크기에서 결과를 확인하세요. 그러면 설명되지 않는 공백을 임의의 오프셋으로 보정하지 않아도 배치를 예측 가능하게 유지할 수 있습니다.

출처