Canvas Path2D SVG: 올바른 크기로 경로 재사용하기
올바른 viewBox 변환, 색상, 구멍, 픽셀 밀도로 SVG 경로 데이터를 캔버스에 재사용하세요. 직접 작성한 예제를 따라 하고 편집 가능한 SVG 마스터를 보관하세요.
목차
경로의 d 속성을 new Path2D(d)에 전달한 뒤 캔버스 컨텍스트로 그리세요. SVG와 일치시키려면 좌표 매핑, 페인팅, 채우기 규칙도 옮겨야 합니다. 생성자는 경로 데이터를 받습니다. SVG 문서를 가져오거나 문서의 viewBox, CSS, 그룹 변환을 적용하지는 않습니다(MDN Path2D 생성자).
사용자 정의 아이콘이 SVG에서는 올바르게 보이지만 캔버스에서는 작아지거나 위치가 어긋나거나 검게 변하거나 구멍이 채워질 때 이 차이가 중요합니다. 아래 예제는 두 경로와 명시적인 맞춤 변환으로 차이를 해결합니다. 기존 벡터 기하학적 형태가 있다면 그것으로 시작하세요. 벡터화는 재사용할 아트워크가 아직 픽셀로만 있을 때 유용합니다.
Path2D 객체에 담기는 것
경로 데이터는 윤곽 설명이라고 생각하세요. 다른 SVG 명령은 그 문자열 밖에 있으므로 캔버스 그리기 코드가 필요한 명령을 제공해야 합니다.
| SVG 입력 | 캔버스에서 처리할 사항 |
|---|---|
경로의 d 값 | Path2D 기하학적 형태 생성 |
viewBox와 표시 크기 | 그리기 변환 계산 |
fill과 fill-rule | fillStyle을 설정하고 fill()에 규칙 전달 |
부모 또는 경로의 transform | 동일한 변환을 별도로 적용 |
| CSS, 그레이디언트, 마스크, 텍스트, 이미지 | 필요한 렌더링을 구현하거나 다른 방식 선택 |
샘플은 명시적인 채우기 값, 직계 자식 경로 두 개를 사용하며 다른 SVG 기능은 없습니다. 살펴볼 수 있도록 의도적으로 작게 구성했습니다. 추출 반복문을 임의의 SVG 가져오기 도구에 복사하면 상속된 스타일과 지원되지 않는 요소가 누락됩니다.
원본과 설정하지 않은 캔버스 비교하기
직접 만든 이 엠블럼에는 둥근 청록색 몸체, 주황색 꼭짓점, 사각형 구멍이 있습니다. viewBox는 (0, 0)이 아닌 (20, 10)에서 시작합니다. 안쪽 하위 경로가 바깥 경계와 같은 방향으로 진행하므로 선언한 evenodd 규칙이 중요합니다.
이 SVG와 캔버스를 HTML 페이지에 넣으세요.
<svg id="source" xmlns="http://www.w3.org/2000/svg"
viewBox="20 10 120 80" width="240" height="160">
<path fill="#126d70" fill-rule="evenodd"
d="M40 20 H90 Q110 20 110 40 V60 Q110 80 90 80 H40 Q30 80 30 70 V30 Q30 20 40 20 Z M50 35 H80 V60 H50 Z"/>
<path fill="#f19b38" d="M115 25 L135 50 L115 75 Z"/>
</svg>
<canvas id="result"></canvas>다른 설정 없이 두 d 값을 반복하며 ctx.fill(new Path2D(d))를 호출하면 기하학적 형태는 캔버스 좌표와 기본 페인팅 및 채우기 규칙을 사용합니다. SVG 외관을 자동으로 가져오지 않습니다. 캔버스 fill()은 경로와 명시적인 evenodd 또는 nonzero 규칙을 받으며 기본값은 nonzero입니다(MDN fill()).

방법: 직접 작성한 이 SVG를 브라우저에서 렌더링하고 두 경로를 두 캔버스 방식으로 그린 뒤 별도의 HTML 페이지에서 공개한 코드를 다시 실행했습니다. 스크린샷은 실제 브라우저 출력입니다. 좌표와 페인팅 처리를 보여주며 PerfectVector 변환은 아닙니다.
viewBox를 캔버스에 매핑하기
SVG의 기본 종횡비 유지 동작은 viewBox를 뷰포트 안에 맞추고 중앙에 배치합니다. SVG 좌표계 명세는 이 매핑을 정의합니다. 이 평면 예제에서는 균일한 배율 하나와 이동으로 xMidYMid meet를 재현하세요.
scale = min(cssWidth / viewBoxWidth, cssHeight / viewBoxHeight)
tx = (cssWidth - viewBoxWidth * scale) / 2 - viewBoxX * scale
ty = (cssHeight - viewBoxHeight * scale) / 2 - viewBoxY * scale마지막 항들은 0이 아닌 원점을 제거합니다. 240 × 160에서는 배율이 2, tx가 -40, ty가 -20입니다. 300 × 120에서는 배율이 1.5가 되며 너비 180 픽셀로 맞춰진 viewBox 양쪽에 수평 공간이 각각 60 픽셀 남습니다. 따라서 원본 원점을 반영한 이동은 (30, -15)입니다.
위 HTML 뒤에 다음 스크립트를 사용하세요. 이 샘플의 직접 지정한 경로 속성을 처리하고 전체 캔버스를 다시 그리며 CSS 크기와 기반 비트맵 크기를 구분합니다.
const source = document.querySelector("#source");
const vb = source.viewBox.baseVal;
const layers = [...source.querySelectorAll("path")].map(el => ({
path: new Path2D(el.getAttribute("d")),
fill: el.getAttribute("fill"),
rule: el.getAttribute("fill-rule") || "nonzero"
}));
function draw(canvas, cssWidth, cssHeight, dpr = window.devicePixelRatio || 1) {
canvas.style.width = `${cssWidth}px`;
canvas.style.height = `${cssHeight}px`;
canvas.width = Math.round(cssWidth * dpr);
canvas.height = Math.round(cssHeight * dpr);
const ctx = canvas.getContext("2d");
const scale = Math.min(cssWidth / vb.width, cssHeight / vb.height);
const tx = (cssWidth - vb.width * scale) / 2 - vb.x * scale;
const ty = (cssHeight - vb.height * scale) / 2 - vb.y * scale;
const px = canvas.width / cssWidth;
const py = canvas.height / cssHeight;
ctx.setTransform(px * scale, 0, 0, py * scale, px * tx, py * ty);
for (const layer of layers) {
ctx.fillStyle = layer.fill;
ctx.fill(layer.path, layer.rule);
}
}
draw(document.querySelector("#result"), 240, 160);
canvas.width와 canvas.height를 설정하면 컨텍스트가 지워지고 초기화됩니다. 함수가 전체 변환을 설정하고 모든 레이어를 다시 칠하므로 반복 호출해도 배율이 누적되지 않습니다. 너비와 높이가 양수인 유효한 viewBox와 양수 요청 치수를 가정합니다.
px와 py는 실제로 반올림된 비트맵 치수를 사용합니다. 요청 밀도로 소수 치수가 생겨도 CSS 좌표가 할당한 픽셀에 맞게 매핑됩니다. MDN devicePixelRatio 예제는 캔버스 표시 크기와 픽셀 치수를 따로 처리해야 하는 이유를 설명합니다.
크기와 픽셀 밀도 따로 확인하기
두 CSS 크기에서 같은 함수에 DPR 값 1과 2를 명시적으로 전달했습니다. 이는 제어된 밀도 입력이며 두 물리적 기기를 테스트했다는 주장이 아닙니다. 기본 호출에서 브라우저가 보고한 기기 픽셀 비율은 2였습니다.
| CSS 크기 | DPR 인수 | 관찰된 캔버스 비트맵 |
|---|---|---|
| 240 × 160 | 1 | 240 × 160 |
| 240 × 160 | 2 | 480 × 320 |
| 300 × 120 | 1 | 300 × 120 |
| 300 × 120 | 2 | 600 × 240 |
네 경우 모두 구멍의 중심은 투명하게 유지되었습니다. 밀도를 높이면 기반 해상도가 바뀌지만 잘못된 변환을 수정하거나 누락된 채우기 규칙을 복원하지는 않습니다. 의도한 CSS 치수나 기기 픽셀 비율이 바뀌면 다시 그리세요. CSS로만 확대하면 이미 캔버스에 있는 비트맵이 늘어납니다.
캔버스에 도달하기 전부터 원본이 잘못되었다면 SVG 크기 및 viewBox 확인을 사용하세요. 캔버스만 잘못되었다면 먼저 정상 SVG 기준 이미지와 변환 및 페인팅 처리를 비교하세요.
원본 보관과 올바른 가져오기 방식 선택하기
추출한 경로 데이터와 함께 SVG 마스터 및 속성을 보관하세요. Path2D 그리기 루틴은 소수 윤곽과 스타일을 직접 제어할 때 유용합니다. 복잡한 SVG에서 브라우저가 렌더링한 외관만 필요하다면 이미지로 불러와 그 이미지를 그리는 방식으로 모든 기능을 다시 구현하는 일을 피할 수 있습니다. 그 방식도 캔버스 비트맵에 칠합니다.
벡터 경로로 그리기 시작했어도 캔버스 출력을 PNG로 저장하면 래스터 이미지가 됩니다. 캔버스 내보내기 API는 이미지 blob을 생성하며 PNG 지원은 필수입니다(MDN toBlob()). 향후 경로 편집을 위해 SVG를 보관하세요. 마찬가지로 SVG 파일에는 유용한 경로 데이터 대신 래스터 이미지가 들어 있을 수 있습니다. 삽입 이미지 진단으로 이런 경우를 구분할 수 있습니다.
사용자 정의 아이콘의 PNG나 JPG만 남아 있고 재사용할 윤곽이 필요할 때 PerfectVector가 적합합니다. 이미지를 SVG로 변환한 뒤 캔버스용 경로를 추출하기 전에 실루엣, 분리된 색상, 작은 구멍을 검사하세요. 깔끔한 SVG 마스터가 있다면 바로 준비 단계로 넘어가세요. 추적이 viewBox 매핑을 구현하거나 CSS를 복사하거나 캔버스 PNG를 편집 가능한 벡터로 만들지는 않습니다.
아이콘 모음 전체에는 각 그리기 루틴을 연결하기 전에 공유 UI 키트 그리드와 페인팅 규칙을 정하세요. 블로그에 더 많은 SVG 준비 예제가 있습니다.
인터페이스에서 사용하기 전 확인하기
- 캔버스와 같은 표시 치수로 SVG 기준 이미지를 확인하세요.
- viewBox의 너비와 높이뿐 아니라 원점도 확인하세요.
- 페인팅과 의도한 채우기 규칙을 옮기고 모든 구멍을 검사하세요.
d만 복사하지 말고 그룹 변환을 반영하세요.- 실제
canvas.width와canvas.height를 읽고 의도한 최대 표시 크기를 테스트하세요. - SVG 마스터를 보관하고 인터페이스에 적합한 접근성 이름이나 주변 텍스트를 제공하세요.
FAQ
Path2D로 전체 SVG 파일을 가져올 수 있나요? 아닙니다. 문자열 입력은 경로의 d 속성 값 같은 SVG 경로 데이터입니다. 문서 구조, viewBox 매핑, 스타일, 그룹 변환은 별도로 처리해야 합니다.
SVG의 구멍이 캔버스에서 채워진 이유는 무엇인가요? 채우기 규칙과 하위 경로 방향을 확인하세요. 이 예제에는 evenodd가 필요하지만 캔버스 fill의 기본값은 nonzero입니다. Path2D 객체를 채울 때 의도한 규칙을 전달하세요.
캔버스에 SVG 경로를 그리면 벡터 출력이 유지되나요? 경로는 프로그램 안에서 재사용 가능한 그리기 명령으로 남지만 캔버스를 PNG로 내보내면 픽셀이 생성됩니다. 편집과 벡터 전달을 위해 원본 SVG를 보관하세요.
Sources
- MDN — Path2D 생성자 — 생성자가 받는 경로 데이터 문자열을 정의합니다.
- W3C — SVG 좌표계 — viewBox 원점, 뷰포트 매핑, 종횡비를 유지하는 정렬을 정의합니다.
- MDN — Canvas fill() — 경로 채우기와 evenodd 및 nonzero 규칙을 설명합니다.
- MDN — devicePixelRatio — CSS 표시 크기와 캔버스 기반 해상도를 설명합니다.
- MDN — Canvas toBlob() — 캔버스의 래스터 이미지 내보내기를 설명합니다.
래스터 아이콘만 있나요? PerfectVector로 SVG를 준비하고 윤곽과 구멍을 확인한 뒤 실제 인터페이스 크기에서 캔버스 그림을 SVG와 비교하세요.

