Canvas API 기초 — 2D 그래픽, 차트, 게임 렌더링
Canvas API는 자바스크립트로 2D 그래픽을 그릴 수 있는 저수준 API입니다. 차트 라이브러리(Chart.js), 게임 엔진(Phaser), 이미지 편집 도구의 기반이 되는 기술입니다.
기본 설정
<canvas id="canvas" width="600" height="400"></canvas>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// 고해상도 디스플레이 대응
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);
도형 그리기
// 사각형
ctx.fillStyle = "#3498db";
ctx.fillRect(10, 10, 100, 80); // 채운 사각형
ctx.strokeStyle = "#e74c3c";
ctx.lineWidth = 3;
ctx.strokeRect(130, 10, 100, 80); // 테두리 사각형
ctx.clearRect(20, 20, 30, 30); // 영역 지우기
// 원
ctx.beginPath();
ctx.arc(300, 50, 40, 0, Math.PI * 2); // x, y, 반지름, 시작각, 끝각
ctx.fillStyle = "#2ecc71";
ctx.fill();
ctx.stroke();
// 선
ctx.beginPath();
ctx.moveTo(10, 150);
ctx.lineTo(100, 200);
ctx.lineTo(200, 160);
ctx.strokeStyle = "#9b59b6";
ctx.lineWidth = 2;
ctx.stroke();
// 삼각형 (경로로 그리기)
ctx.beginPath();
ctx.moveTo(300, 150);
ctx.lineTo(350, 230);
ctx.lineTo(250, 230);
ctx.closePath();
ctx.fillStyle = "#e67e22";
ctx.fill();
텍스트
ctx.font = "bold 24px Arial";
ctx.fillStyle = "#2c3e50";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Hello Canvas!", 300, 300);
// 텍스트 윤곽선
ctx.strokeStyle = "#e74c3c";
ctx.lineWidth = 1;
ctx.strokeText("Outline Text", 300, 340);
// 텍스트 너비 측정
const metrics = ctx.measureText("Hello");
console.log("너비:", metrics.width);
그라디언트와 패턴
// 선형 그라디언트
const gradient = ctx.createLinearGradient(0, 0, 200, 0);
gradient.addColorStop(0, "#3498db");
gradient.addColorStop(1, "#e74c3c");
ctx.fillStyle = gradient;
ctx.fillRect(10, 10, 200, 100);
// 원형 그라디언트
const radial = ctx.createRadialGradient(150, 150, 10, 150, 150, 80);
radial.addColorStop(0, "yellow");
radial.addColorStop(1, "red");
ctx.fillStyle = radial;
ctx.beginPath();
ctx.arc(150, 150, 80, 0, Math.PI * 2);
ctx.fill();
이미지 그리기
const img = new Image();
img.src = "photo.jpg";
img.onload = () => {
// 기본 그리기
ctx.drawImage(img, 0, 0);
// 크기 지정
ctx.drawImage(img, 0, 0, 200, 150);
// 이미지 잘라서 그리기 (소스 영역 → 캔버스 영역)
ctx.drawImage(img, 50, 50, 100, 100, 0, 0, 200, 200);
};
간단한 막대 차트
function drawBarChart(data, labels) {
const chartWidth = 500;
const chartHeight = 300;
const barWidth = chartWidth / data.length - 10;
const maxValue = Math.max(...data);
ctx.clearRect(0, 0, canvas.width, canvas.height);
data.forEach((value, i) => {
const barHeight = (value / maxValue) * chartHeight;
const x = i * (barWidth + 10) + 50;
const y = chartHeight - barHeight + 30;
// 막대
ctx.fillStyle = `hsl(${i * 60}, 70%, 50%)`;
ctx.fillRect(x, y, barWidth, barHeight);
// 값 표시
ctx.fillStyle = "#333";
ctx.font = "14px Arial";
ctx.textAlign = "center";
ctx.fillText(value, x + barWidth / 2, y - 10);
// 라벨
ctx.fillText(labels[i], x + barWidth / 2, chartHeight + 50);
});
}
drawBarChart([120, 80, 200, 150, 90], ["월", "화", "수", "목", "금"]);
변환 (Transform)
// 저장 / 복원
ctx.save(); // 현재 상태 저장
ctx.translate(200, 200); // 원점 이동
ctx.rotate(Math.PI / 4); // 45도 회전
ctx.scale(1.5, 1.5); // 1.5배 확대
ctx.fillRect(-25, -25, 50, 50); // 변환된 좌표계에서 그리기
ctx.restore(); // 이전 상태 복원
간단한 게임 — 공 튕기기
class Ball {
constructor(x, y) {
this.x = x;
this.y = y;
this.radius = 15;
this.dx = 3;
this.dy = 3;
this.color = "#e74c3c";
}
draw(ctx) {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
update(width, height) {
// 벽 충돌 감지
if (this.x + this.radius > width || this.x - this.radius < 0) {
this.dx = -this.dx;
}
if (this.y + this.radius > height || this.y - this.radius < 0) {
this.dy = -this.dy;
}
this.x += this.dx;
this.y += this.dy;
}
}
const ball = new Ball(100, 100);
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ball.update(600, 400);
ball.draw(ctx);
requestAnimationFrame(gameLoop);
}
gameLoop();
마우스 이벤트와 Canvas
function getMousePos(canvas, event) {
const rect = canvas.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top,
};
}
// 그리기 도구
let isDrawing = false;
canvas.addEventListener("mousedown", (e) => {
isDrawing = true;
const pos = getMousePos(canvas, e);
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
});
canvas.addEventListener("mousemove", (e) => {
if (!isDrawing) return;
const pos = getMousePos(canvas, e);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
});
canvas.addEventListener("mouseup", () => {
isDrawing = false;
});
Canvas를 이미지로 내보내기
// Data URL로 변환
const dataURL = canvas.toDataURL("image/png");
// Blob으로 변환 (다운로드용)
canvas.toBlob((blob) => {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "canvas.png";
a.click();
URL.revokeObjectURL(url);
}, "image/png");
Canvas vs SVG
| 항목 | Canvas | SVG |
|---|---|---|
| 렌더링 | 래스터 (픽셀) | 벡터 |
| DOM 접근 | 불가 | 가능 |
| 이벤트 처리 | 좌표 계산 필요 | 요소별 이벤트 |
| 대량 객체 | 우수 | 느림 |
| 확대 | 깨짐 | 깨지지 않음 |
| 적합한 용도 | 게임, 이미지 편집 | 아이콘, 차트, 지도 |
**기억하기 **: Canvas는 픽셀 기반 그래픽으로, 게임이나 대량 데이터 시각화에 적합합니다. SVG는 벡터 기반으로 확대해도 깨지지 않고 DOM 이벤트를 사용할 수 있어 인터랙티브 차트에 적합합니다. 실무에서는 대부분 Chart.js나 D3.js 같은 라이브러리를 통해 사용합니다.
댓글 로딩 중...