Canvas API/Canvas tutorial/Drawing shapes with canvas

김동현·2026년 3월 22일

Canvas로 도형 그리기

◀ 이전: Basic usage | 다음: Applying styles and colors ▶

이제 canvas 환경을 설정했으니, canvas에 그리는 방법의 세부 사항을 살펴볼 수 있어요. 이 글을 다 읽고 나면, 사각형, 삼각형, 선, 호, 곡선을 그리는 방법을 배우게 되어 몇 가지 기본 도형에 익숙해질 거예요. Canvas에 객체를 그릴 때 경로(path)를 다루는 것은 필수적이고, 어떻게 할 수 있는지 알아볼 거예요.

💡 강사 팁: Canvas로 도형 그리기는 Canvas API의 가장 기초적이면서도 중요한 부분이에요! 마치 어릴 적 그림판으로 그림 그리던 것과 비슷한데, 코드로 한다는 차이가 있죠.

Canvas 그리기의 핵심 개념:

  1. 좌표계 이해하기: Canvas는 왼쪽 위가 (0, 0)이에요. x축은 오른쪽으로, y축은 아래로 증가합니다. 수학 시간에 배운 좌표계와 y축이 반대라서 처음에 헷갈릴 수 있어요.

  2. 경로(Path) 개념: Canvas에서 도형을 그리는 핵심은 경로예요. beginPath()로 시작해서, 여러 점을 찍거나 선을 그은 다음, stroke()fill()로 실제로 그려요. 이걸 이해하면 복잡한 도형도 쉽게 그릴 수 있어요.

제 경험상 초보자들이 자주 하는 실수:

  • beginPath() 빼먹기: 새로운 도형을 그릴 때마다 beginPath()를 호출해야 해요. 안 그러면 이전 경로와 연결되어 엉뚱한 결과가 나와요. 이거 정말 많이 실수하는 부분이에요!

  • stroke()fill() 차이 혼동: stroke()는 테두리만, fill()은 내부를 채워요. 둘 다 사용할 수도 있어요. 순서도 중요한데, 보통 fill() 먼저 하고 stroke()를 나중에 해야 테두리가 선명해요.

실용적인 팁:

// 기본 패턴 - 이걸 외우세요!
ctx.beginPath();        // 1. 새 경로 시작
ctx.moveTo(x, y);       // 2. 시작점 이동
ctx.lineTo(x, y);       // 3. 선 그리기
ctx.stroke();           // 4. 실제로 그리기
  • 사각형은 특별해요: fillRect(), strokeRect() 같은 전용 메서드가 있어서 경로 없이 바로 그릴 수 있어요. 빠르고 편리하답니다!

  • 원/호 그리기는 수학이 필요해요: arc() 메서드는 라디안(radian)을 사용해요. 각도를 라디안으로 변환하려면 degrees * Math.PI / 180을 사용하세요. 저도 매번 찾아보곤 했어요 😅

  • 디버깅 팁: 처음에는 strokeStyle을 빨간색이나 파란색 같이 눈에 띄는 색으로 설정하세요. 도형이 어디에 그려지는지 확인하기 쉬워요!

다음에 배울 스타일과 색상 적용이 더 재미있어요! 지금은 기본 도형 그리기에 집중하고, 나중에 그라데이션이나 패턴으로 멋지게 꾸밀 수 있답니다. 차근차근 따라가다 보면 어느새 복잡한 그래픽도 그릴 수 있게 될 거예요!

캔버스 그리드 및 도형 그리기 (The Grid & Drawing shapes)

안녕하세요! 캔버스 마스터하기 두 번째 시간입니다. 본격적으로 화려한 그림을 그리기 전에, 캔버스의 도화지가 어떻게 생겨먹었는지 그 뼈대부터 확실히 이해하고 넘어가야겠죠? 공식 문서의 내용을 아주 알기 쉽게 풀어서 설명해 드릴게요.


그리드 (The grid)

그림을 그리기 전에 가장 먼저 알아야 할 것은 캔버스의 그리드(grid), 즉 좌표 공간(coordinate space)입니다. 이전 페이지에서 우리가 만들었던 HTML 뼈대를 떠올려보세요. 너비가 150픽셀이고 높이가 150픽셀인 캔버스 요소가 있었죠.

일반적으로 이 그리드 안의 1단위(unit)는 캔버스 상의 딱 1픽셀(pixel)에 해당합니다. 이 캔버스 세계에서 가장 중요한 원점(origin), 즉 좌표 (0,0)은 어디일까요? 바로 수학 시간과 다르게 '왼쪽 위(top left) 모서리'에 위치해 있습니다!

캔버스 안의 모든 요소들은 바로 이 원점 (0,0)을 기준으로 배치됩니다. 만약 여러분이 파란색 사각형을 하나 그린다면, 그 사각형의 왼쪽 위 모서리 위치는 원점에서부터 오른쪽으로 x픽셀, 아래쪽으로 y픽셀만큼 이동한 좌표인 (x,y)가 되는 것입니다.

물론 캔버스가 항상 이렇게 고정된 건 아닙니다. 튜토리얼을 진행하다 보면 원점의 위치를 다른 곳으로 이동(translate)시키거나, 그리드 전체를 회전(rotate)시키고, 심지어 확대/축소(scale)하는 방법도 배우게 될 텐데요. 지금 당장은 머리 아프니까 이 기본(default) 상태만 기억하고 넘어갈게요!

💡 강사의 팁: 수학 그래프에 익숙하신 분들은 y축이 아래로 갈수록 숫자가 커진다는 점이 처음엔 엄청 헷갈리실 거예요. "브라우저는 모니터의 왼쪽 위 끝에서부터 빛을 쏘면서 화면을 그린다"라고 생각하시면 이해하기 편합니다!


사각형 그리기 (Drawing rectangles)

SVG와는 다르게, <canvas>는 기본적으로 딱 두 가지 형태의 원시적인 도형(primitive shapes)만 지원합니다. 바로 사각형(rectangles)경로(paths, 선으로 연결된 점들의 집합)입니다. 이 외의 동그라미, 별, 다각형 같은 모든 다른 도형들은 무조건 하나 이상의 경로(path)를 요리조리 조합해서 만들어내야 합니다.

다행히도, 캔버스에는 아주 복잡한 모양도 만들어 낼 수 있는 다양한 경로 그리기 함수들이 준비되어 있으니 너무 걱정 마세요.

하지만 지금은 캔버스가 유일하게 편애하는 기초 도형, '사각형'부터 살펴보겠습니다. 캔버스에 사각형을 그리는 함수는 딱 세 가지가 있습니다.

fillRect(x, y, width, height)

색칠된 꽉 찬 사각형을 그립니다.

strokeRect(x, y, width, height)

사각형의 윤곽선(테두리)만 그립니다.

clearRect(x, y, width, height)

지정된 사각형 영역 안의 내용을 싹 지워버리고, 그 자리를 완전히 투명(transparent)하게 만듭니다. (일종의 지우개 역할이죠!)

이 세 가지 함수는 완전히 똑같은 매개변수(parameters)를 받습니다. xy는 사각형의 왼쪽 위 모서리가 캔버스 위(원점 기준)에서 어디에 위치할지를 정합니다. 그리고 widthheight는 사각형의 크기(너비와 높이)를 지정합니다.

아래 코드는 이전 페이지에서 보았던 draw() 함수인데, 이번에는 방금 배운 이 세 가지 함수를 모두 사용하도록 수정해 보았습니다.

사각형 모양 예제 (Rectangular shape example)

<canvas id="canvas" width="150" height="150"></canvas>
function draw() {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");

  ctx.fillRect(25, 25, 100, 100);
  ctx.clearRect(45, 45, 60, 60);
  ctx.strokeRect(50, 50, 50, 50);
}

이 예제의 결과는 어떻게 나올까요? 머릿속으로 상상해 보세요.

  1. fillRect() 함수가 각 변의 길이가 100픽셀인 커다란 검은색 꽉 찬 사각형을 그립니다. (색상을 안 정하면 기본은 검은색입니다!)
  2. 그다음 clearRect() 함수가 그 검은 사각형 정중앙에서 60x60픽셀 크기의 정사각형 모양을 투명하게 지워버립니다. (가운데가 뻥 뚫린 네모난 도넛 모양이 되었네요!)
  3. 마지막으로 strokeRect()가 지워진 투명한 네모 공간 안쪽에 다시 50x50픽셀 크기의 네모난 윤곽선을 그립니다. (개념적으로는 50x50이지만, 실제로는 선의 두께 때문에 52x52가 됩니다. 왜 그런지는 바로 다음 섹션에서 설명할게요.)

다음 튜토리얼에서는 clearRect()를 대체할 수 있는 두 가지 다른 방법과, 그려진 도형들의 색상이나 테두리 스타일을 어떻게 바꾸는지 배우게 될 거예요.

💡 중요: 다음 섹션에서 배울 경로(path) 함수들과는 다르게, 방금 배운 이 세 가지 사각형 함수들은 호출되는 즉시(immediately) 캔버스에 그림을 쾅! 하고 그려버립니다. 경로를 그리고 나서 칠하라고 명령을 내려야 하는 번거로움이 없죠.


가장자리가 흐릿하게 보이나요? (Seeing blurry edges?)

위의 사각형 예제나, 앞으로 보게 될 여러 예제들을 따라 하다 보면 한 가지 거슬리는 점을 발견할 수 있습니다. 바로 캔버스에 그린 도형들의 가장자리(선)가 SVG나 CSS로 그린 것보다 어딘가 미묘하게 흐릿(blurry)해 보일 수 있다는 점입니다.

이것은 캔버스 API가 날카로운 선을 그리지 못해서가 아닙니다! 진짜 이유는 캔버스의 논리적인 그리드가 화면의 실제 물리적인 픽셀에 매핑(mapping)되는 방식 때문이며, 때로는 브라우저가 캔버스를 임의로 스케일링(확대/축소)하는 방식 때문이기도 합니다.

이 현상이 잘 이해가 안 가신다면, 캔버스를 CSS로 억지로 크게 확대해 보겠습니다.

<canvas id="canvas" width="15" height="15"></canvas>
#canvas {
  width: 300px;
  height: 300px;
}
function draw() {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");
  ctx.strokeRect(2, 2, 10, 10);
  ctx.fillRect(7, 7, 1, 1);
}

이 예제에서 우리는 '진짜 해상도'는 아주 작은 15x15 크기의 캔버스를 만들었습니다. 하지만 CSS를 사용해서 이 캔버스를 300x300 픽셀로 강제로 늘려버렸죠! 그 결과, 캔버스 안의 1픽셀은 화면상에서 20x20 크기의 커다란 픽셀 블록으로 나타나게 됩니다.

그곳에 (2,2)부터 (12,12)까지 테두리가 있는 사각형을 그리고, 가운데 (7,7) 위치에 1x1 크기의 점(꽉 찬 사각형)을 그렸습니다. 결과물을 보면 테두리가 엄청나게 흐릿하게 보일 겁니다.

왜냐하면 브라우저가 비트맵(raster) 이미지를 확대할 때, 부족한 픽셀 사이를 부드럽게 채우기 위해 기본적으로 안티앨리어싱(smoothing algorithm, 보간법)을 사용하기 때문입니다. 이 기능은 복잡한 사진이나 곡선 그래픽에는 환상적이지만, 우리가 그린 것처럼 딱딱 떨어지는 직선 형태의 도형에는 오히려 독이 됩니다.

이 블러 현상을 고치고 레트로 게임처럼 픽셀이 뚝뚝 끊어지게(crisp) 만들고 싶다면, CSS의 image-rendering 속성을 pixelated로 설정하면 됩니다.

#canvas {
  image-rendering: pixelated;
}

이제 브라우저가 캔버스를 확대할 때 억지로 픽셀을 문지르지 않고, 원래의 각진 픽셀 느낌을 최대한 그대로 유지하게 됩니다.

참고: 날카로운 선을 유지하기 위해 image-rendering: pixelated를 쓰는 것이 완벽한 만병통치약은 아닙니다. 화면의 CSS 픽셀이 기기의 실제 물리적 픽셀과 딱 맞아떨어지지 않을 때 (devicePixelRatio가 정수가 아닐 때), 어떤 픽셀은 다른 픽셀보다 더 뚱뚱하게 그려져서 모양이 불규칙해 보일 수 있습니다. CSS 픽셀이 기기 픽셀에 정확히 1:1로 대응하지 못하는 상황에서는 픽셀을 균일하게 채우는 것이 불가능하기 때문에 해결하기 꽤 까다로운 문제입니다.

좌표 0.5의 마법 (The half-pixel problem)

하지만 CSS 스케일링 문제를 해결하고 나서도 또 다른 문제가 눈에 띕니다. 처음에 그렸던 사각형 예제에서도 볼 수 있는 현상인데요. 선 두께가 1픽셀인 strokeRect()를 그렸더니 1픽셀이 아니라 2픽셀처럼 두껍게 보이고, 색깔도 까만색이 아니라 옅은 회색으로 보이죠? 이 현상은 캔버스가 좌표를 도형의 경계선으로 해석하는 독특한 방식 때문에 발생합니다.

위의 그리드 다이어그램 이미지를 다시 잘 살펴보세요. 212 같은 좌표는 픽셀의 정중앙을 가리키는 것이 아니라, 픽셀과 픽셀 사이의 경계선(edge)을 가리킵니다!

위 이미지에서 굵은 격자 선은 캔버스의 좌표 그리드이고, 그 사이의 네모칸 하나하나가 화면에 표시되는 진짜 픽셀입니다.
첫 번째 이미지를 보세요. (2,1)부터 (5,5)까지 색을 꽉 채웠습니다(빨간색 영역). 이 경우 지정된 영역이 픽셀의 경계선과 정확히 일치하므로 아주 날카롭고 선명한 사각형이 그려집니다.

하지만 두 번째 이미지를 보세요. (3,1)에서 (3,5)까지 이어지는 선 두께(line thickness)가 1.0인 선을 그렸다고 생각해 봅시다. 수학적으로 이 선(진한 파란색 영역)은 좌표 3이라는 경계선을 중심으로 양옆으로 0.5픽셀씩만 뻗어나갑니다.
문제는 모니터 화면에는 '0.5픽셀'이라는 게 존재하지 않는다는 점입니다! 모니터는 오직 1픽셀 단위로만 색을 칠할 수 있어요.

그래서 브라우저는 이 0.5픽셀을 표현하기 위해 일종의 꼼수(근사치)를 씁니다. 바로 양옆의 픽셀(2번과 3번 픽셀 칸)을 절반의 농도로 연하게 칠해버리는 거죠! 그 결과 원래 그렸어야 할 진한 1픽셀짜리 선 대신, 두께는 2픽셀이 되고 색깔은 원래 색보다 반으로 옅어진 흐리멍덩한 선(연파랑 + 진파랑 영역)이 그려지게 됩니다. 이것이 아까 strokeRect()에서 선이 두껍고 회색으로 보였던 이유입니다.

이 현상을 고치려면, 경로를 생성할 때 위치를 아주 정교하게 잡아주어야 합니다. 선 두께 1.0짜리 선이 경계선 양쪽으로 0.5 유닛씩 뻗어나간다는 사실을 깨달았으니, 처음부터 선을 그릴 때 경계선이 아니라 픽셀의 정중앙(0.5가 더해진 위치)에서 시작하면 어떨까요?

세 번째 이미지가 바로 그 해결책입니다! (3.5, 1)에서 (3.5, 5)로 선을 그리면, 3.5라는 픽셀 중앙에서 양옆으로 0.5픽셀씩 뻗어나가면서 결국 3번 픽셀 칸 하나를 온전히 꽉 채우게 됩니다. 그 결과 우리가 그토록 원하던 날카로운 1픽셀짜리 수직선이 만들어지는 것이죠.

참고: 위 예시에서 수직선을 그릴 때 X좌표는 3.5를 주었지만, Y좌표는 여전히 1과 5라는 정수 값을 썼다는 점에 주의하세요. 만약 Y좌표에도 0.5를 줬다면, 선의 양 끝점(위/아래)이 또다시 픽셀에 반만 걸치게 되어서 끝부분이 흐릿해졌을 겁니다.

그래서 우리가 앞서 사각형 예제에서 strokeRect(50, 50, 50, 50)를 호출했을 때, 이 사각형은 개념적으로는 50x50이었지만 실제 렌더링된 크기는 52x52가 된 것입니다. 선을 그리기 위한 영역이 (49.5, 49.5)에서 시작해 (100.5, 100.5)에서 끝났고, 이 반쪽짜리 픽셀들을 채우기 위해 픽셀 하나를 통째로 쓰다 보니 실제 칠해진 영역은 (49, 49)부터 (101, 101)까지가 되어서 2픽셀 두께의 52x52 사각형이 된 거죠.

만약 정확히 50x50 크기의 1픽셀짜리 날카로운 윤곽선을 얻고 싶다면, 윤곽선의 두께(1px)만큼 사각형의 크기를 줄이고, 두께의 절반(0.5px)만큼 위치를 이동시켜야 합니다. (으악, 계산이 복잡하죠? 😅)

function draw() {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");
  
  // (2,2) 대신 (2.5, 2.5) 위치에서 크기를 1씩 줄여서 (9,9)로 그립니다!
  ctx.strokeRect(2.5, 2.5, 9, 9);
  ctx.fillRect(7, 7, 1, 1);
}

짝수 두께(2, 4, 6...)의 선을 그릴 때는 픽셀의 정중앙에서 양옆으로 1픽셀, 2픽셀씩 딱 떨어지게 뻗어나가므로 0.5를 더해줄 필요가 없습니다. 즉, 홀수 두께의 선을 그릴 때만 픽셀 사이(0.5)에 맞추고, 짝수 두께의 선을 그릴 때는 정수 좌표에 맞춰야 깔끔하게 나옵니다.

처음 확장 가능한 2D 그래픽을 다룰 때는 이런 픽셀 그리드와 경로 위치를 일일이 신경 쓰는 게 꽤나 골치 아플 수 있습니다. 하지만 이 원리만 잘 이해하고 그려두면, 나중에 캔버스를 확대/축소하거나 어떤 변형을 가하더라도 항상 올바르고 깔끔한 이미지를 유지할 수 있습니다. 0.5 위치에 제대로 그려진 1.0 두께의 선을 나중에 2배로 확대하면? 흐려지지 않고 아주 선명하고 날카로운 2픽셀짜리 선이 제자리에 떡하니 나타나게 될 겁니다.


경로 그리기 (Drawing paths)

자, 이제 두 번째 도형인 경로(paths)에 대해 알아봅시다. 경로는 쉽게 말해 선으로 연결된 '점들의 목록'입니다. 이 선들은 직선일 수도, 곡선일 수도 있고 굵기나 색상도 마음대로 바꿀 수 있습니다. 심지어 하나의 경로나 하위 경로(subpath)를 완전히 닫아버릴(도형을 완성할) 수도 있죠. 경로를 이용해서 도형을 만들려면 약간의 추가적인 절차가 필요합니다.

  1. 먼저, 경로를 생성(시작)합니다.
  2. 그다음, 그리기 명령어들(drawing commands)을 사용해서 경로 안으로 선을 이어갑니다.
  3. 경로가 다 만들어졌다면, 이제 그 경로에 윤곽선을 그리거나(stroke) 속을 채워서(fill) 화면에 렌더링합니다.

이 단계들을 수행하기 위해 사용하는 함수들은 다음과 같습니다.

beginPath()

새로운 경로를 생성합니다. 이 함수를 호출한 이후부터 내려지는 그리기 명령어들은 모두 이 경로 안으로 들어가서 경로를 구성하게 됩니다.

Path methods (경로 메서드들)

선을 긋거나 호를 그리는 등 객체의 경로를 구체적으로 설정하는 다양한 메서드들입니다.

closePath()

현재 점 위치에서 출발점(현재 하위 경로의 시작점)까지 직선을 쫙 그어서 도형을 닫아버립니다.

stroke()

지금까지 그린 경로를 따라 윤곽선을 그립니다. (선을 눈에 보이게 만듭니다!)

fill()

지금까지 그린 경로의 내부 영역을 색으로 꽉 채워서 솔리드한 도형을 만듭니다.

경로를 생성하는 첫 번째 단계는 무조건 beginPath()를 호출하는 것입니다. 내부적으로 경로는 도형을 구성하는 하위 경로들(직선, 호 등)의 리스트로 저장되는데요, 이 beginPath() 메서드를 호출할 때마다 기존의 리스트가 완전히 초기화(reset)되고 백지상태에서 새로운 도형을 그리기 시작할 수 있게 됩니다.

참고: beginPath()를 막 호출했거나 캔버스를 갓 생성해서 현재 경로가 텅 비어있는 상태라면, 여러분이 처음으로 내리는 그리기 명령어는 그게 어떤 것이든 상관없이 무조건 moveTo()(펜 이동하기)로 취급됩니다. 따라서 경로를 리셋한 후에는 항상 내가 그림을 그리기 시작할 '시작점'을 명확하게 지정해 주는 것이 중요합니다.

두 번째 단계는 우리가 그릴 경로를 명시하는 실제 그리기 메서드들을 호출하는 것입니다. (이 메서드들은 잠시 후에 구체적으로 살펴볼 거예요.)

세 번째 단계는 선택 사항(optional)인데, 바로 closePath()를 호출하는 것입니다. 이 메서드는 현재 펜이 있는 위치에서 제일 처음 시작했던 점까지 일직선을 그어서 도형을 닫으려고 시도합니다. 만약 도형이 이미 닫혀있거나 경로에 점이 딱 하나밖에 없다면 이 함수는 아무 일도 하지 않습니다.

참고 (아주 중요해요!): 여러분이 경로를 그린 후 fill()(채우기)을 호출하면, 열려있는 도형이라 할지라도 브라우저가 알아서 시작점과 끝점을 연결해 닫아버린 다음 색을 칠합니다. 따라서 fill()을 쓸 때는 굳이 closePath()를 호출할 필요가 없습니다. 하지만 stroke()(윤곽선 그리기)를 호출할 때는 다릅니다! stroke()는 자동으로 도형을 닫아주지 않기 때문에, 완전히 닫힌 윤곽선을 그리고 싶다면 반드시 stroke() 전에 closePath()를 명시적으로 호출해야 합니다.

삼각형 그리기 (Drawing a triangle)

말로만 들으면 헷갈리죠? 실제로 삼각형을 그리는 코드를 통해 경로의 마법을 확인해 봅시다!

<canvas id="canvas" width="100" height="100"></canvas>
function draw() {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");

  // 1. 새로운 경로의 시작을 선언합니다!
  ctx.beginPath();
  
  // 2. 펜을 (75, 50) 좌표로 번쩍 들어서 이동시킵니다. (그리진 않고 이동만!)
  ctx.moveTo(75, 50);
  
  // 3. (75, 50)에서 (100, 75)까지 쭈욱 선을 긋습니다.
  ctx.lineTo(100, 75);
  
  // 4. (100, 75)에서 (100, 25)까지 쭈욱 선을 긋습니다.
  ctx.lineTo(100, 25);
  
  // 5. 속을 칠하라고 명령합니다. 
  // (이때 열려있던 (100, 25)와 시작점 (75, 50) 사이는 자동으로 닫히면서 색이 채워집니다!)
  ctx.fill();
}

이 코드를 실행하면 짜잔! 화면에 멋진 검은색 직각 삼각형이 나타날 것입니다.

안녕하세요! 캔버스의 기초에 이어 이제 본격적으로 '경로(Path)를 그려서 다양한 도형을 만드는 법'을 공부하시는군요.

프론트엔드 개발자로서 캔버스로 커스텀 차트, 다이어그램, 혹은 간단한 게임 UI를 그릴 때 이 부분은 정말 필수적입니다. 우리가 종이 위에 펜을 들고 점을 찍어 선을 긋고, 곡선을 만들고, 마지막에 색을 칠하는 과정과 완벽하게 똑같다고 생각하시면 이해하기 쉬울 거예요.

자, 펜을 들고 캔버스 위를 누벼볼까요? 강사의 실무 팁과 함께 꼼꼼하게 번역해 드릴게요!


펜 이동하기 (Moving the pen)

실제로 무언가를 그리는 것은 아니지만, 위에서 설명한 경로(path) 목록의 일부분이 되는 아주 유용한 함수가 하나 있습니다. 바로 moveTo() 함수입니다. 이 함수는 종이 위 한 지점에서 펜이나 연필을 들어 올려 다른 지점에 살포시 내려놓는 동작이라고 생각하시면 딱 맞습니다.

moveTo(x, y)

펜의 위치를 지정된 xy 좌표로 이동시킵니다.

캔버스가 초기화되거나 beginPath()가 호출되었을 때, 여러분은 보통 시작점을 특정 위치로 옮기기 위해 이 moveTo() 함수를 사용하게 될 것입니다. 또한 서로 연결되지 않은 독립적인 경로들을 그릴 때도 moveTo()를 유용하게 쓸 수 있죠. 아래에 나올 스마일 페이스(smiley face) 예제를 한 번 살펴보세요.

직접 테스트해 보려면 아래 코드 조각을 사용하면 됩니다. 우리가 이전 장에서 보았던 draw() 함수 안에 이 코드를 그냥 붙여넣기만 하세요.

<canvas id="canvas" width="150" height="150"></canvas>
function draw() {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");

  ctx.beginPath();
  ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // 바깥쪽 큰 원 (얼굴 윤곽)
  ctx.moveTo(110, 75);
  ctx.arc(75, 75, 35, 0, Math.PI, false); // 입 (시계 방향으로 반원)
  ctx.moveTo(65, 65);
  ctx.arc(60, 65, 5, 0, Math.PI * 2, true); // 왼쪽 눈
  ctx.moveTo(95, 65);
  ctx.arc(90, 65, 5, 0, Math.PI * 2, true); // 오른쪽 눈
  ctx.stroke(); // 테두리를 그립니다!
}

결과는 아래와 같습니다:

(스마일 페이스 그림)

만약 moveTo()를 호출하는 코드 줄들을 지우면 어떻게 될까요? 눈과 입, 그리고 얼굴 윤곽을 그리는 선들이 서로 떨어지지 않고 펜을 떼지 않은 채로 그려지기 때문에 엉뚱한 연결선들이 죽 그어진 모습을 보게 될 것입니다.

참고:
arc() 함수에 대해 더 자세히 알고 싶다면, 아래의 원호(Arcs) 섹션을 참조하세요.


선 긋기 (Lines)

직선을 그리기 위해서는 lineTo() 메서드를 사용합니다.

lineTo(x, y)

현재 드로잉 위치(펜이 있는 곳)에서 xy로 지정된 위치까지 직선을 긋습니다.

이 메서드는 선의 끝점이 될 좌표인 xy, 두 개의 인자를 받습니다. 선의 시작점은 이전에 그려진 경로들에 의해 결정됩니다. 즉, 이전 경로의 끝점이 다음 경로의 시작점이 되는 식이죠. 물론 시작점은 앞서 배운 moveTo() 메서드를 사용해서 언제든지 다른 곳으로 바꿀 수 있습니다.

아래 예제는 두 개의 삼각형을 그립니다. 하나는 색이 채워져 있고(filled), 다른 하나는 테두리(outlined)만 그려져 있습니다.

<canvas id="canvas" width="150" height="150"></canvas>
function draw() {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");

  // 색칠된 삼각형 (Filled triangle)
  ctx.beginPath();
  ctx.moveTo(25, 25); // 시작점으로 펜 이동
  ctx.lineTo(105, 25); // 오른쪽으로 선 긋기
  ctx.lineTo(25, 105); // 대각선 아래로 선 긋기
  ctx.fill(); // 색을 채움 (자동으로 닫힘)

  // 테두리만 있는 삼각형 (Stroked triangle)
  ctx.beginPath();
  ctx.moveTo(125, 125); // 시작점으로 펜 이동
  ctx.lineTo(125, 45); // 위로 선 긋기
  ctx.lineTo(45, 125); // 대각선 아래로 선 긋기
  ctx.closePath(); // 시작점과 끝점을 연결하여 경로를 닫음
  ctx.stroke(); // 테두리를 그림
}

이 코드는 새로운 도형 경로를 시작하기 위해 beginPath()를 호출하는 것으로 출발합니다. 그런 다음 moveTo() 메서드를 사용해서 원하는 위치로 시작점을 옮기죠. 그 밑으로는 삼각형의 두 변을 이루는 두 개의 선을 긋고 있습니다.

색칠된 삼각형과 테두리만 있는 삼각형 사이에 차이점이 눈에 띄실 겁니다. 위에서 언급했듯이, 경로에 fill()(채우기)을 실행하면 도형은 자동으로 닫히게 되지만, stroke()(선 긋기)를 할 때는 자동으로 닫히지 않기 때문입니다. 만약 테두리 삼각형을 그릴 때 closePath()를 빼먹었다면, 완벽한 삼각형이 아니라 그저 'ㄴ'자나 'ㄱ'자 모양의 두 개의 선만 덜렁 그려졌을 것입니다.


원호 (Arcs)

원호(arc)나 완벽한 원을 그리기 위해서는 arc() 또는 arcTo() 메서드를 사용합니다.

arc(x, y, radius, startAngle, endAngle, counterclockwise)

(x, y) 위치를 중심으로 하고 반지름이 r인 원의 곡선을 따라, startAngle(시작 각도)에서 시작하여 endAngle(끝 각도)에서 끝나는 원호를 그립니다. 방향은 counterclockwise(반시계 방향 여부) 인자에 의해 결정됩니다 (기본값은 시계 방향입니다).

arcTo(x1, y1, x2, y2, radius)

지정된 제어점들(control points)과 반지름을 사용하여 원호를 그리고, 이전 점과 이 원호의 시작점을 직선으로 연결합니다.

여섯 개의 매개변수를 가지는 arc 메서드를 조금 더 자세히 살펴보겠습니다. xy는 호를 그릴 바탕이 되는 원의 '중심' 좌표입니다. radius는 말 그대로 반지름이고요. startAngleendAngle 매개변수는 원의 곡선을 따라 위치할 호의 시작점과 끝점을 라디안(radians) 단위로 정의합니다. 이 각도들은 x축을 기준으로 측정됩니다. counterclockwise 매개변수는 불리언(Boolean, 참/거짓) 값인데, true로 설정하면 원호를 반시계 방향으로 그리고, 그렇지 않으면 시계 방향으로 그립니다.

참고:
arc 함수에서 각도는 각도(degrees, 예: 90도, 180도)가 아니라 라디안(radians)으로 측정된다는 사실에 각별히 주의하세요! 각도를 라디안으로 변환하려면 다음 자바스크립트 공식을 사용하시면 됩니다: radians = (Math.PI/180)*degrees.

다음 예제는 앞서 보았던 것들보다 살짝 더 복잡합니다. 각기 다른 각도와 채우기 방식을 가진 12개의 다양한 호를 그립니다.

두 개의 for 루프는 원호들을 행(row)과 열(column)로 배치하기 위해 사용되었습니다. 각각의 호를 그릴 때마다 beginPath()를 호출하여 새로운 경로를 시작합니다. 코드 내에서 가독성을 높이기 위해 호를 그리는 매개변수들을 각각 변수에 담아두었지만, 실제 실무에서는 꼭 이렇게 일일이 변수로 뺄 필요는 없습니다.

xy 좌표가 어떻게 잡히는지는 코드를 보면 명확할 것입니다. radiusstartAngle은 고정되어 있습니다. endAngle은 첫 번째 열에서 180도(반원)로 시작해서 열이 넘어갈 때마다 90도씩 점점 커지다가, 마지막 열에서는 완벽한 원을 이루게 됩니다.

counterclockwise 매개변수에 들어가는 조건문은 첫 번째와 세 번째 행이 시계 방향 호로, 두 번째와 네 번째 행이 반시계 방향 호로 그려지도록 만듭니다. 마지막으로, if 조건문을 사용하여 위쪽 절반의 호들은 테두리(stroke)만 그리고, 아래쪽 절반의 호들은 색을 채우도록(fill) 처리했습니다.

참고:
이 예제를 제대로 보려면 이 페이지의 다른 예제들보다 조금 더 큰 캔버스가 필요합니다: 150 x 200 픽셀 크기입니다.

<canvas id="canvas" width="150" height="200"></canvas>
function draw() {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");

  for (let i = 0; i < 4; i++) {
    for (let j = 0; j < 3; j++) {
      ctx.beginPath();
      const x = 25 + j * 50; // x 좌표 계산
      const y = 25 + i * 50; // y 좌표 계산
      const radius = 20; // 호의 반지름
      const startAngle = 0; // 원의 시작점 (3시 방향)
      const endAngle = Math.PI + (Math.PI * j) / 2; // 원의 끝점 계산
      const counterclockwise = i % 2 !== 0; // 홀수 행은 반시계, 짝수 행은 시계방향

      ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise);

      if (i > 1) {
        ctx.fill(); // 3, 4번째 행은 색 채우기
      } else {
        ctx.stroke(); // 1, 2번째 행은 테두리만 그리기
      }
    }
  }
}

베지어 및 2차 곡선 (Bezier and quadratic curves)

다음으로 사용할 수 있는 경로의 유형은 바로 베지어 곡선(Bézier curves)입니다. 3차(cubic) 곡선과 2차(quadratic) 곡선 두 가지 종류가 모두 제공됩니다. 이 곡선들은 주로 물방울이나 나뭇잎 같은 복잡하고 유기적인 형태의 도형을 그릴 때 사용됩니다.

quadraticCurveTo(cp1x, cp1y, x, y)

현재 펜 위치에서 시작하여 cp1xcp1y로 지정된 **제어점(control point)**을 사용하여, xy로 지정된 끝점까지 2차 베지어 곡선을 그립니다.

bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

현재 펜 위치에서 시작하여 (cp1x, cp1y)와 (cp2x, cp2y)로 지정된 **두 개의 제어점**을 사용하여, xy로 지정된 끝점까지 3차 베지어 곡선을 그립니다.

이 둘의 차이점을 설명하자면, 2차 베지어 곡선은 시작점과 끝점(파란색 점)이 있고 제어점이 딱 하나(빨간색 점으로 표시됨)인 반면, 3차 베지어 곡선은 두 개의 제어점을 사용한다는 것입니다.

이 두 메서드에서 xy 매개변수는 언제나 선이 끝나는 '끝점'의 좌표입니다. cp1xcp1y는 첫 번째 제어점의 좌표이고, cp2xcp2y는 두 번째 제어점의 좌표입니다.

💡 강사의 핵심 보충 설명:
코드로 베지어 곡선을 그리는 건 생각보다 꽤 어렵습니다! 왜냐하면 Adobe Illustrator나 Figma 같은 벡터 드로잉 툴과는 달리, 선이 어떻게 휘어지고 있는지 눈으로 바로 보면서 마우스로 당겨볼 수 없기 때문이죠.
실무에서 복잡한 아이콘이나 로고를 캔버스로 그려야 한다면, 처음부터 좌표를 계산해서 치기보다는 일러스트레이터나 SVG에서 경로(Path) 데이터를 추출해서 사용하는 방식을 강력히 추천합니다! (이 부분은 아래 Path2D 설명에서 나옵니다.)

이 예제들 자체에 대단히 어려운 내용은 없습니다. 두 경우 모두 둥근 곡선들을 연속해서 그려 최종적으로 하나의 완성된 형태를 만들어내는 것을 볼 수 있습니다.

2차 베지어 곡선 (Quadratic Bezier curves)

이 예제는 말풍선 모양을 렌더링하기 위해 여러 개의 2차 베지어 곡선을 사용합니다.

<canvas id="canvas" width="150" height="150"></canvas>
function draw() {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");

  // 2차 곡선 예제 (말풍선 그리기)
  ctx.beginPath();
  ctx.moveTo(75, 25);
  ctx.quadraticCurveTo(25, 25, 25, 62.5);
  ctx.quadraticCurveTo(25, 100, 50, 100);
  ctx.quadraticCurveTo(50, 120, 30, 125);
  ctx.quadraticCurveTo(60, 120, 65, 100);
  ctx.quadraticCurveTo(125, 100, 125, 62.5);
  ctx.quadraticCurveTo(125, 25, 75, 25);
  ctx.stroke();
}

3차 베지어 곡선 (Cubic Bezier curves)

이 예제는 3차 베지어 곡선을 사용해서 아름다운 하트 모양을 그립니다.

<canvas id="canvas" width="150" height="150"></canvas>
function draw() {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");

  // 3차 곡선 예제 (하트 그리기)
  ctx.beginPath();
  ctx.moveTo(75, 40);
  ctx.bezierCurveTo(75, 37, 70, 25, 50, 25);
  ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);
  ctx.bezierCurveTo(20, 80, 40, 102, 75, 120);
  ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5);
  ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
  ctx.bezierCurveTo(85, 25, 75, 37, 75, 40);
  ctx.fill();
}

직사각형 (Rectangles)

이전 튜토리얼의 '직사각형 그리기' 파트에서 보았던, 캔버스 위에 직사각형 모양을 곧바로 그려버리는 세 가지 메서드 외에도 rect()라는 메서드가 하나 더 있습니다. 이 메서드는 현재 열려있는 경로(path)에 직사각형 모양의 경로를 추가하는 역할을 합니다.

rect(x, y, width, height)

지정된 widthheight를 가지고, 좌측 상단 모서리가 (x, y)에 위치하는 직사각형 경로를 그립니다.

이 메서드가 실행되기 직전에, moveTo() 메서드가 (x, y) 매개변수를 가지고 내부적으로 자동으로 호출됩니다. 다시 말해, 펜의 현재 위치가 직사각형의 시작점 좌표로 알아서 재설정된다는 뜻입니다. (이후에 stroke()fill()을 호출해야 화면에 나타납니다.)


조합해서 그리기 (Making combinations)

지금까지 이 페이지의 각 예제에서는 한 도형을 그릴 때 오직 한 가지 종류의 경로 함수만을 사용했습니다. 하지만 하나의 도형을 만들기 위해 사용할 수 있는 경로의 수나 종류에는 전혀 제한이 없습니다. 그러니 마지막 예제로, 지금까지 배운 모든 경로 함수들을 짬뽕해서 아주 유명한 게임 캐릭터(팩맨과 유령) 세트를 만들어 보겠습니다.

<canvas id="canvas" width="200" height="185"></canvas>
function draw() {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");

  // 모서리가 둥근 사각형들을 그려서 게임 보드를 만듭니다.
  roundedRect(ctx, 12, 12, 184, 168, 15);
  roundedRect(ctx, 19, 19, 170, 154, 9);
  roundedRect(ctx, 53, 53, 49, 33, 10);
  roundedRect(ctx, 53, 119, 49, 16, 6);
  roundedRect(ctx, 135, 53, 49, 33, 10);
  roundedRect(ctx, 135, 119, 25, 49, 10);

  // 팩맨 그리기
  ctx.beginPath();
  ctx.arc(37, 37, 13, Math.PI / 7, -Math.PI / 7, false);
  ctx.lineTo(31, 37);
  ctx.fill();

  // 점(먹이) 그리기 루프
  for (let i = 0; i < 8; i++) {
    ctx.fillRect(51 + i * 16, 35, 4, 4);
  }
  for (let i = 0; i < 6; i++) {
    ctx.fillRect(115, 51 + i * 16, 4, 4);
  }
  for (let i = 0; i < 8; i++) {
    ctx.fillRect(51 + i * 16, 99, 4, 4);
  }

  // 유령 캐릭터 그리기 (베지어 곡선과 직선 조합)
  ctx.beginPath();
  ctx.moveTo(83, 116);
  ctx.lineTo(83, 102);
  ctx.bezierCurveTo(83, 94, 89, 88, 97, 88);
  ctx.bezierCurveTo(105, 88, 111, 94, 111, 102);
  ctx.lineTo(111, 116);
  ctx.lineTo(106.333, 111.333);
  ctx.lineTo(101.666, 116);
  ctx.lineTo(97, 111.333);
  ctx.lineTo(92.333, 116);
  ctx.lineTo(87.666, 111.333);
  ctx.lineTo(83, 116);
  ctx.fill(); // 몸통 색 채우기

  // 유령의 하얀 눈자위 그리기
  ctx.fillStyle = "white";
  ctx.beginPath();
  ctx.moveTo(91, 96);
  ctx.bezierCurveTo(88, 96, 87, 99, 87, 101);
  ctx.bezierCurveTo(87, 103, 88, 106, 91, 106);
  ctx.bezierCurveTo(94, 106, 95, 103, 95, 101);
  ctx.bezierCurveTo(95, 99, 94, 96, 91, 96);
  ctx.moveTo(103, 96);
  ctx.bezierCurveTo(100, 96, 99, 99, 99, 101);
  ctx.bezierCurveTo(99, 103, 100, 106, 103, 106);
  ctx.bezierCurveTo(106, 106, 107, 103, 107, 101);
  ctx.bezierCurveTo(107, 99, 106, 96, 103, 96);
  ctx.fill();

  // 유령의 까만 눈동자 그리기
  ctx.fillStyle = "black";
  ctx.beginPath();
  ctx.arc(101, 102, 2, 0, Math.PI * 2, true);
  ctx.fill();
  ctx.beginPath();
  ctx.arc(89, 102, 2, 0, Math.PI * 2, true);
  ctx.fill();
}

// 모서리가 둥근 직사각형을 그리기 위해 직접 만든 유틸리티(도우미) 함수
function roundedRect(ctx, x, y, width, height, radius) {
  ctx.beginPath();
  ctx.moveTo(x, y + radius);
  ctx.arcTo(x, y + height, x + radius, y + height, radius);
  ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius);
  ctx.arcTo(x + width, y, x + width - radius, y, radius);
  ctx.arcTo(x, y, x, y + radius, radius);
  ctx.stroke();
}

이 코드가 만들어내는 결과 이미지는 아래와 같습니다:

(팩맨 게임 화면 일부 렌더링 결과)

코드가 꽤 길지만 실제로 아주 놀라울 정도로 단순한 구조이기 때문에 하나하나 깊이 파고들지는 않겠습니다. 여기서 눈여겨봐야 할 가장 중요한 점은 드로잉 컨텍스트에서 fillStyle 속성을 어떻게 활용하는지, 그리고 자주 쓰이는 그리기 로직을 묶어서 roundedRect() 같은 유틸리티(도우미) 함수로 만들어 쓴다는 점입니다. 이렇게 유틸리티 함수를 만들면 필요한 코드의 양을 확 줄이고 복잡도를 낮추는 데 엄청난 도움이 됩니다.

fillStyle에 대해서는 이 튜토리얼의 뒷부분에서 훨씬 자세히 다룰 예정입니다. 여기서는 단순히 붓의 잉크 색깔을 기본색인 검은색에서 흰색으로 바꿨다가, 다시 검은색으로 되돌리는 용도로만 사용했습니다.


구멍이 뚫린 도형 (Shapes with holes)

도넛이나 도장처럼 가운데에 구멍이 뚫린 도형을 그리고 싶다면 어떻게 해야 할까요? 도형의 바깥쪽 테두리를 그릴 때와 다른 반대 방향(시계 방향 vs 반시계 방향)으로 안쪽 구멍의 테두리를 그려주면 됩니다!
즉, 바깥쪽 도형을 시계 방향으로 그렸다면 안쪽 구멍은 반시계 방향으로 그리고, 바깥쪽을 반시계로 그렸다면 안쪽은 시계 방향으로 그려야 합니다. (이것을 컴퓨터 그래픽스에서는 Non-zero winding rule이라고 부릅니다.)

<canvas id="canvas" width="150" height="150"></canvas>
function draw() {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");

  ctx.beginPath();

  // 바깥쪽 도형은 시계 방향으로 ⟳
  ctx.moveTo(0, 0);
  ctx.lineTo(150, 0);
  ctx.lineTo(75, 129.9);

  // 안쪽 구멍은 반시계 방향으로 ↺
  ctx.moveTo(75, 20);
  ctx.lineTo(50, 60);
  ctx.lineTo(100, 60);

  ctx.fill(); // 색을 채우면 신기하게도 안쪽 삼각형 모양이 투명하게 구멍 뚫린 채로 채워집니다!
}

위의 예제에서 바깥쪽 삼각형은 시계 방향으로 돌아가고 (좌측 상단에서 우측 상단으로 선을 긋고, 마지막에 바닥으로 내려옴), 안쪽 삼각형은 반시계 방향으로 돌아갑니다 (위쪽 꼭짓점에서 좌측 하단으로 선을 긋고, 우측 하단으로 마무리).


Path2D 객체 (Path2D objects)

마지막 예제에서 보았듯이, 캔버스에 복잡한 도형을 하나 그리려면 수많은 경로 그리기 명령들을 일일이 줄 세워야 합니다. 코드를 단순화하고 성능을 높이기 위해, 최신 브라우저에서는 Path2D 객체를 제공합니다. 이 객체를 사용하면 일련의 그리기 명령들을 미리 묶어서 캐싱(저장)하거나 녹화해 둘 수 있습니다. 그리고 원할 때 이 묶음(경로)을 아주 빠르게 여러 번 캔버스에 찍어낼 수 있죠.

Path2D 객체를 어떻게 생성하는지 볼까요?

Path2D()

Path2D() 생성자는 새로운 Path2D 인스턴스 객체를 반환합니다. 선택적으로 다른 경로 객체를 인자로 넘겨서 똑같은 복사본을 만들 수도 있고, SVG 경로(path) 데이터로 구성된 문자열을 넘겨서 경로를 생성할 수도 있습니다.

new Path2D(); // 텅 빈 경로 객체를 만듭니다.
new Path2D(path); // 기존에 있던 다른 Path2D 객체를 똑같이 복사합니다.
new Path2D(d); // SVG 경로 데이터 문자열을 바탕으로 경로 객체를 만듭니다.

우리가 앞서 배웠던 moveTo, rect, arc, quadraticCurveTo 등 모든 경로 그리기 메서드들을 캔버스 컨텍스트(ctx) 대신 Path2D 객체에 대고 똑같이 사용할 수 있습니다.

또한, Path2D API는 여러 개의 경로를 하나로 합칠 수 있는 addPath라는 유용한 메서드를 제공합니다. 여러 부품 컴포넌트들을 조립해서 하나의 완성된 큰 오브젝트를 만들 때 아주 쏠쏠하게 쓸 수 있습니다.

Path2D.addPath(path [, transform])

현재 경로에 또 다른 경로를 덧붙입니다. 선택적으로 변형 매트릭스(transform)를 적용할 수도 있습니다.

Path2D 예제 (Path2D example)

이 예제에서는 직사각형과 원을 하나씩 생성합니다. 둘 다 나중에 다시 꺼내 쓸 수 있도록 각각 독립적인 Path2D 객체에 저장해 두었죠.
새로운 Path2D API가 도입되면서, 캔버스 컨텍스트의 여러 메서드들(예: stroke, fill 등)이 업데이트되어 현재 열려있는 경로 대신 미리 만들어둔 Path2D 객체를 인자로 넘겨받을 수 있게 되었습니다. 아래 코드에서는 strokefill 함수에 방금 만든 경로 객체를 쏙 집어넣어 캔버스에 한 방에 렌더링하고 있습니다.

<canvas id="canvas" width="130" height="100"></canvas>
function draw() {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");

  // 직사각형의 그리기 명령을 담은 Path2D 도장 만들기
  const rectangle = new Path2D();
  rectangle.rect(10, 10, 50, 50);

  // 원의 그리기 명령을 담은 Path2D 도장 만들기
  const circle = new Path2D();
  circle.arc(100, 35, 25, 0, 2 * Math.PI);

  // 만들어둔 도장을 ctx에 쾅 찍기!
  ctx.stroke(rectangle); // 직사각형은 테두리만 그리기
  ctx.fill(circle); // 원은 색을 칠해서 그리기
}

SVG 경로 활용하기 (Using SVG paths)

새로운 캔버스 Path2D API의 숨겨진 강력한 무기는 바로 SVG 경로 데이터(SVG path data)를 문자열 그대로 사용해서 캔버스의 경로를 세팅할 수 있다는 것입니다! 이를 통해 경로 데이터를 서버에서 받아오거나 어플리케이션 안에서 손쉽게 굴릴 수 있으며, 같은 데이터를 SVG 태그와 Canvas 렌더링 모두에서 완벽하게 재사용할 수 있습니다.

💡 강사의 핵심 팁:
위에서 베지어 곡선 그리기 어려우셨죠? 디자이너분이 일러스트레이터나 피그마에서 그려준 SVG 파일의 코드를 열어보면 <path d="M10 10 h 80 v 80 h -80 Z" /> 같은 이상한 암호문이 있을 거예요. 저 d 안에 있는 문자열을 그대로 복사해서 new Path2D("여기에 붙여넣기") 하시면 캔버스 위에 디자이너가 의도한 복잡한 아이콘이나 곡선이 완벽하게 나타납니다!

// SVG 패스 코드를 바로 집어넣어서 경로를 만듭니다.
const p = new Path2D("M10 10 h 80 v 80 h -80 Z");

이 경로 문자열은 점 (M10 10)으로 이동한 다음, 오른쪽으로 80포인트 수평 이동(h 80), 아래로 80포인트 수직 이동(v 80), 왼쪽으로 80포인트 수평 이동(h -80)을 하고, 마지막으로 원래 시작점으로 닫아서 돌아오라는(Z) 뜻입니다.

이전 (Previous) | 다음 (Next)


이번 시간에는 선을 긋고, 호를 그리고, 복잡한 곡선과 구멍 뚫린 도형, 그리고 실무에서 정말 사랑받는 Path2D와 SVG 연동까지 아주 꽉 찬 내용들을 배웠습니다!

profile
프론트에_가까운_풀스택_개발자

0개의 댓글