
웹에서 2D 그래픽을 그릴 수 있는 대표적인 방법으로 Canvas API가 있다. HTML5부터 표준에 포함된 Canvas는
<canvas>
라는 HTML 태그를 사용하여 픽셀 단위의 2D 그래픽을 다룰 수 있게 해준다. 간단한 그림부터 복잡한 게임, 데이터 시각화, 애니메이션 구현까지 폭넓게 활용할 수 있다.
Canvas 기초
Canvas 요소 삽입
Canvas를 사용하기 위해서는 HTML 문서에 <canvas>
태그를 배치해야 한다. 이때 id
, width
, height
등의 속성을 설정할 수 있다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>Canvas 기초</title>
<style>
/* Canvas를 눈에 잘 보이도록 테두리를 추가 */
#myCanvas {
border: 1px solid #ccc;
background-color: #f9f9f9;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="600" height="400"></canvas>
<script src="main.js"></script>
</body>
</html>
- width, height: 캔버스의 해상도를 의미하며, 기본값은 300×150이다. CSS로 사이즈를 조절할 수도 있지만, 실제 픽셀 해상도를 관리하려면
width
와height
속성을 직접 지정하는 편이 좋다. - CSS에서
width
,height
를 변경하면 요소의 표시 크기는 달라지지만 내부 해상도가 달라지지 않으므로 그림이 왜곡될 수 있다.
렌더링 컨텍스트(Context)
JavaScript에서 Canvas를 다루기 위해서는 Rendering Context를 가져와야 한다. 2D 그래픽을 그릴 때는 "2d"
라는 문자열로 지정한다.
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 여기서부터 추가적인 예제 코드 작성
// ctx(렌더링 컨텍스트)를 이용해 도형이나 텍스트, 이미지 등을 그릴 수 있다.
getContext('2d')
: 2D 그리기용 컨텍스트 객체를 반환한다.- WebGL 같은 3D 그래픽을 사용하려면
getContext('webgl')
또는getContext('experimental-webgl')
을 사용할 수 있다.
기본 도형
2D 컨텍스트를 이용하면 선(line), 사각형(rect), 원(arc), 텍스트(text) 등을 손쉽게 그릴 수 있다. 마치 그림판(Paint)처럼 그려나가지만, 프로그래밍적으로 제어한다는 점이 다르다.
사각형(Rectangle)
Canvas API에는 사각형을 그리는 몇 가지 편의 메서드가 있다.
fillRect(x, y, width, height)
: 사각형 내부를 색으로 채운다.strokeRect(x, y, width, height)
: 사각형 윤곽선만 그린다.clearRect(x, y, width, height)
: 지정된 영역을 지워 투명하게 만든다.
// 사각형 채우기
ctx.fillStyle = 'skyblue'; // 사각형을 채울 색상
ctx.fillRect(20, 20, 100, 100); // (x=20, y=20) 위치에 100×100 크기 사각형
// 사각형 윤곽선 그리기
ctx.strokeStyle = 'red'; // 윤곽선 색상
ctx.lineWidth = 2; // 선 굵기
ctx.strokeRect(150, 20, 100, 100);
// 사각형 부분 지우기
ctx.clearRect(30, 30, 20, 20);
// (30, 30) ~ (50, 50) 영역을 지워 투명하게 만든다.

선(Line) 그리기
선, 경로, 도형 등을 세밀하게 그리려면 경로(Path) 개념을 알아야 한다.
beginPath()
로 경로를 시작하고, moveTo(x, y)
로 시작점을 옮긴 뒤 lineTo(x, y)
로 직선을 긋는다. 모든 경로 명령을 마친 후 stroke()
나 fill()
을 호출하면 실제로 캔버스에 그려진다.
ctx.beginPath();
ctx.moveTo(50, 200); // 시작점
ctx.lineTo(150, 250); // 경로 1
ctx.lineTo(50, 300); // 경로 2
ctx.closePath(); // 시작점과 마지막 점을 연결
ctx.strokeStyle = '#333';
ctx.lineWidth = 3;
ctx.stroke();
beginPath()
: 새로운 경로를 시작한다(이전에 사용하던 경로가 있다면 초기화한다).moveTo()
: 펜을 특정 위치로 옮긴다(이 시점에서는 선이 그려지지 않는다).lineTo()
: 현재 펜 위치에서 지정 위치까지 선을 그린다.stroke()
: 윤곽선을 그리는 메서드다.fill()
: 내부를 채우는 메서드다.

원(Arc) 그리기
원이나 호(arc)를 그리려면 arc(x, y, radius, startAngle, endAngle, counterclockwise?)
를 사용한다. 각도는 라디안 값으로 전달해야 한다.
ctx.beginPath();
ctx.arc(300, 100, 50, 0, Math.PI * 2); // 360도 원
ctx.fillStyle = 'green';
ctx.fill();
// 호(arc)만 그려보기: 0 ~ 180도(π 라디안)
ctx.beginPath();
ctx.arc(450, 100, 50, 0, Math.PI);
ctx.strokeStyle = 'orange';
ctx.lineWidth = 4;
ctx.stroke();
-
arc(x, y, radius, startAngle, endAngle, counterclockwise?)
x
,y
: 원의 중심 좌표radius
: 반지름startAngle
,endAngle
: 호를 그릴 시작 각도와 끝 각도(라디안)counterclockwise
:true
면 반시계 방향,false
면 시계 방향(기본값)

Color, Gradient
Canvas에서는 다양한 방법으로 색상을 지정하고, 그라디언트나 패턴을 적용할 수 있다.
- 색상 지정:
fillStyle
,strokeStyle
- 그라디언트: 선형, 방사형
- 패턴: 이미지를 반복해 도형 내부 채우기
색상 지정
그리기 연산 시 fillStyle
과 strokeStyle
을 통해 그릴 색을 지정할 수 있다. 색상은 문자열 형태로 다양한 표현 방식(RGB, RGBA, HEX, HSL 등)을 지원한다.
ctx.fillStyle = 'rgb(255, 0, 0)';
ctx.strokeStyle = '#00ff00';
그라디언트(Gradient)
Canvas API는 선형(Linear)과 방사(Radial) 그라디언트를 지원한다. 예를 들어, 선형 그라디언트는 다음과 같이 생성할 수 있다.
const gradient = ctx.createLinearGradient(0, 0, 200, 0);
// 시작점(0,0) -> 끝점(200,0)에 걸쳐 색상 변화를 만든다.
gradient.addColorStop(0, 'blue');
gradient.addColorStop(0.5, 'yellow');
gradient.addColorStop(1, 'red');
ctx.fillStyle = gradient;
ctx.fillRect(20, 100, 200, 100);
createLinearGradient(x0, y0, x1, y1)
: 시작점과 끝점을 설정해 그라디언트 객체를 만든다.addColorStop(offset, color)
: 0~1 사이의offset
지점에color
를 배치한다.

텍스트 그리기
Canvas 컨텍스트에 텍스트를 그릴 때는 fillText()
나 strokeText()
를 사용한다.
ctx.font = 'bold 24px sans-serif';
ctx.fillStyle = '#000';
ctx.fillText('Canvas Text Example', 20, 50);
// 윤곽선 텍스트
ctx.strokeStyle = 'blue';
ctx.lineWidth = 1;
ctx.strokeText('Stroke Text', 20, 80);
ctx.font
: CSS 문법과 유사한 형태로 글꼴, 크기, 스타일 등을 지정한다.fillText(text, x, y)
: 텍스트 내부를 채워 그린다.strokeText(text, x, y)
: 텍스트 윤곽선을 그린다.

이미지 그리기
외부 이미지를 불러와 캔버스에 그릴 수도 있다. 이미지는 비동기 로드이므로 img.onload
내에서 그려야 에러 없이 표시할 수 있다. 예전 포스팅에서 사용했던 이미지를 불러와 보았다.
const myImage = new Image();
myImage.src = 'https://raw.githubusercontent.com/yhuj79/blog-assets/refs/heads/main/240713/car/car-1.webp';
myImage.onload = function() {
ctx.drawImage(myImage, 10, 10, 500, 300);
};
drawImage(image, dx, dy)
: 이미지를 (dx, dy) 위치에 원본 크기로 그린다.drawImage(image, dx, dy, dWidth, dHeight)
: 지정된 크기로 축소하거나 확대해 그린다.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
: 원본 이미지의 특정 영역(sx, sy, sWidth, sHeight)을 잘라서 (dx, dy) 위치에 (dWidth, dHeight) 크기로 그린다.

변형(Transformations)
Canvas에서는 translate
, rotate
, scale
등을 통해 좌표계 자체를 변형할 수 있다. 변형을 적용하면 그 이후에 그려지는 도형, 텍스트, 이미지 등이 모두 이동, 회전, 확대/축소되어 표현된다.
// 1) translate (평행 이동)
ctx.translate(300, 200);
// (300,200) 좌표가 새로운 (0,0)이 된다.
// 2) rotate (회전)
ctx.rotate((Math.PI / 180) * 45);
// 45도 회전 (라디안)
// 3) scale (배율)
ctx.scale(1.5, 0.5);
// x축은 1.5배, y축은 0.5배
ctx.fillStyle = 'violet';
ctx.fillRect(0, 0, 100, 100);
// 변형 복원을 위해서는 save(), restore()를 사용한다.
save()
: 현재 컨텍스트 상태(변환, 스타일 등)를 스택에 저장한다.restore()
: 스택에서 마지막으로 저장한 컨텍스트 상태를 불러온다(변환 이전 상태로 되돌린다).

애니메이션 만들기
Canvas는 매 프레임마다 화면을 지우고(clearRect
), 새로운 상태를 그려나가는 방식으로 애니메이션을 구현한다.
requestAnimationFrame
애니메이션을 매끄럽게 구현하려면 setInterval
대신 requestAnimationFrame()
을 사용하는 것이 일반적이다. requestAnimationFrame
은 브라우저가 최적의 타이밍에 콜백 함수를 호출해주므로 CPU 사용률을 조절하며 부드러운 애니메이션을 가능하게 해준다.
let xPos = 0;
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 도형을 그리고 위치를 갱신한다.
ctx.fillStyle = 'blue';
ctx.fillRect(xPos, 100, 50, 50);
xPos += 2;
if (xPos > canvas.width) {
xPos = -50; // 화면 밖으로 나가면 다시 시작하도록 한다.
}
requestAnimationFrame(animate); // 다음 프레임을 요청한다.
}
animate(); // 애니메이션을 시작한다.
주요 흐름
- 매 프레임마다 캔버스를 초기화(
clearRect
)한다. - 물체의 위치 혹은 상태를 변경한다.
- 변경된 상태를 다시 그린다.
requestAnimationFrame(animate)
로 다음 프레임을 예약한다.

고해상도(레티나 디스플레이) 대응
레티나(HiDPI) 디스플레이에서 캔버스를 선명하게 그리려면, CSS 크기와 width
, height
속성을 다르게 설정해 픽셀 농도를 조정해야 한다.
예를 들어, 2배 해상도가 필요한 경우 다음과 같이 설정할 수 있다.
const dpr = window.devicePixelRatio || 1;
canvas.width = 600 * dpr;
canvas.height = 400 * dpr;
canvas.style.width = '600px';
canvas.style.height = '400px';
ctx.scale(dpr, dpr);
// 캔버스 스케일을 조정한다.
이렇게 하면 CSS 상의 크기는 600×400이지만 실제 픽셀 해상도는 1200×800이므로, 레티나 환경에서도 선명한 화면을 제공할 수 있다.
기타 관리 기능
-
save()
/restore()
- 도형을 그리면서 스타일(색상, 선 두께)을 자주 바꾸거나 복잡한 변환을 여러 번 적용한다면,
save()
,restore()
를 통해 컨텍스트 상태를 기억하고 복구할 수 있다.
- 도형을 그리면서 스타일(색상, 선 두께)을 자주 바꾸거나 복잡한 변환을 여러 번 적용한다면,
-
캔버스 지우기
- 매 프레임이나 필요할 때 전체 화면을 지울 때는
ctx.clearRect(0, 0, canvas.width, canvas.height)
를 사용한다.
- 매 프레임이나 필요할 때 전체 화면을 지울 때는
-
성능 최적화
- 더블 버퍼링처럼 보조 캔버스를 활용하거나, 최소 영역만 재렌더링하는 기법을 사용하면 성능을 개선할 수 있다.
- 복잡한 애니메이션은 CPU/GPU 사용량이 높으므로 필요하다면 WebGL로 전환하거나 GPU 가속이 가능한 라이브러리를 고려해볼 수 있다.
-
유틸 함수 작성
- 예:
drawCircle(x, y, r, color)
처럼 자주 쓰이는 도형을 함수화하거나, 애니메이션 루프용 클래스(혹은 모듈)를 만들어 재사용성을 높이면 편리하다.
- 예:
마무리 종합 예제
지금까지 살펴본 도형, 색상, 애니메이션을 간단히 결합한 예시 코드이다. Html로 작성하였다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8"/>
<title>Canvas 종합 예제</title>
<style>
#canvasExample {
border: 1px solid #999;
}
</style>
</head>
<body>
<canvas id="canvasExample" width="600" height="400"></canvas>
<script>
const canvas = document.getElementById('canvasExample');
const ctx = canvas.getContext('2d');
let xPos = 50;
let yPos = 50;
let xSpeed = 2;
let ySpeed = 2;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 사각형을 그린다.
ctx.fillStyle = 'skyblue';
ctx.fillRect(0, 0, canvas.width, canvas.height / 2);
// 텍스트를 그린다.
ctx.font = 'bold 20px sans-serif';
ctx.fillStyle = '#fff';
ctx.fillText('Canvas 2D Demo', 20, 40);
// 원(움직이는 공)을 그린다.
ctx.beginPath();
ctx.arc(xPos, yPos, 20, 0, Math.PI * 2);
ctx.fillStyle = 'red';
ctx.fill();
// 위치를 업데이트한다.
xPos += xSpeed;
yPos += ySpeed;
// 범위를 벗어나면 속도 방향을 반전시킨다.
if (xPos < 20 || xPos > canvas.width - 20) xSpeed *= -1;
if (yPos < 20 || yPos > canvas.height - 20) ySpeed *= -1;
requestAnimationFrame(draw);
}
draw();
</script>
</body>
</html>

텍스트가 그려져 있고, 화면의 절반은 파란색 사각형으로 채우고, 움직이는 원이 화면의 경계에 닿으면 방향을 반전시킨다.