Canvas 초기화 및 기본 설정
Canvas 요소를 생성하고 2D 렌더링 컨텍스트를 획득하는 것으로 그래픽 프로그래밍을 시작할 수 있습니다.
const canvas = document.querySelector('#myCanvas');
const ctx = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 600;
선 그리기와 경로 관리
선을 그릴 때는 beginPath()로 새로운 경로를 시작하는 것이 중요합니다. 이 메서드를 사용하지 않으면 스타일이 마지막 설정으로 덮어씌워지는 상태 누적 문제가 발생합니다.
// 올바른 선 그리기 방법
ctx.lineWidth = 8;
ctx.strokeStyle = '#3498db';
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(250, 150);
ctx.lineTo(50, 250);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(300, 50);
ctx.lineTo(500, 150);
ctx.lineTo(300, 250);
ctx.strokeStyle = '#e74c3c';
ctx.stroke();
경로를 닫으려면 closePath()를 사용합니다. 이 메서드는 현재 지점에서 시작 지점으로 직선을 그어 닫습니다.
사각형 그리기
사각형을 그리는 세 가지 주요 방법이 있습니다.
// 1. rect() 메서드 사용
ctx.rect(100, 100, 150, 100);
ctx.fillStyle = 'rgba(52, 152, 219, 0.7)';
ctx.fill();
// 2. fillRect() 메서드로 직접 채우기
ctx.fillStyle = 'rgba(231, 76, 60, 0.5)';
ctx.fillRect(300, 100, 150, 100);
// 3. strokeRect() 메서드로 테두리만 그리기
ctx.lineWidth = 3;
ctx.strokeStyle = '#2ecc71';
ctx.strokeRect(500, 100, 150, 100);
별 모양 그리기
삼각함수를 활용하여 복잡한 도형을 생성할 수 있습니다. 다음은 별을 그리는 함수입니다.
function createStar(ctx, centerX, centerY, outerRadius, innerRadius, rotation) {
ctx.beginPath();
for (let i = 0; i < 5; i++) {
const outerAngle = (rotation + i * 72 - 90) * Math.PI / 180;
const innerAngle = (rotation + i * 72 - 36) * Math.PI / 180;
ctx.lineTo(
centerX + Math.cos(outerAngle) * outerRadius,
centerY + Math.sin(outerAngle) * outerRadius
);
ctx.lineTo(
centerX + Math.cos(innerAngle) * innerRadius,
centerY + Math.sin(innerAngle) * innerRadius
);
}
ctx.closePath();
}
// 200개의 랜덤 별 생성
for (let i = 0; i < 200; i++) {
const radius = Math.random() * 15 + 10;
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
const angle = Math.random() * 360;
createStar(ctx, x, y, radius, radius * 0.4, angle);
ctx.fillStyle = `hsl(${Math.random() * 60 + 30}, 80%, 60%)`;
ctx.fill();
}
변환(Transform) 활용
그래픽 변환은 translate(), rotate(), scale()로 제어합니다. save()와 restore()를 사용하여 상태를 관리하면 예상치 못한 결과를 방지할 수 있습니다.
function drawTransformedStar(ctx, x, y, size, rotation) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rotation * Math.PI / 180);
ctx.scale(size, size);
// 단위 크기의 별 경로 정의
ctx.beginPath();
for (let i = 0; i < 5; i++) {
const outerAngle = (i * 72 - 90) * Math.PI / 180;
const innerAngle = (i * 72 - 36) * Math.PI / 180;
ctx.lineTo(Math.cos(outerAngle), Math.sin(outerAngle));
ctx.lineTo(Math.cos(innerAngle) * 0.4, Math.sin(innerAngle) * 0.4);
}
ctx.closePath();
ctx.fillStyle = '#f1c40f';
ctx.fill();
ctx.restore();
}
그라데이션 효과
선형 그라데이션과 원형 그라데이션을 생성할 수 있습니다.
// 선형 그라데이션
const linearGrad = ctx.createLinearGradient(0, 0, 800, 0);
linearGrad.addColorStop(0, '#ff6b6b');
linearGrad.addColorStop(0.5, '#4ecdc4');
linearGrad.addColorStop(1, '#45b7d1');
ctx.fillStyle = linearGrad;
ctx.fillRect(0, 0, 800, 400);
// 원형 그라데이션
const radialGrad = ctx.createRadialGradient(400, 600, 0, 400, 600, 300);
radialGrad.addColorStop(0, '#ffe66d');
radialGrad.addColorStop(1, '#ff6b6b');
ctx.fillStyle = radialGrad;
ctx.fillRect(0, 400, 800, 400);
패턴 채우기
이미지나 다른 Canvas를 반복 패턴으로 사용할 수 있습니다.
// 이미지 패턴
const img = new Image();
img.src = 'assets/texture.png';
img.onload = () => {
const pattern = ctx.createPattern(img, 'repeat');
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 800, 600);
};
// Canvas 패턴
function createCustomPattern() {
const patternCanvas = document.createElement('canvas');
patternCanvas.width = 40;
patternCanvas.height = 40;
const pctx = patternCanvas.getContext('2d');
pctx.strokeStyle = '#34495e';
pctx.lineWidth = 2;
pctx.strokeRect(5, 5, 30, 30);
return patternCanvas;
}
const customPattern = ctx.createPattern(createCustomPattern(), 'repeat');
ctx.fillStyle = customPattern;
ctx.fillRect(0, 0, 800, 600);
둥근 사각형 그리기
네 꼭지점에 원호를 그려 둥근 사각형을 만들 수 있습니다.
function drawRoundedRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 1.5);
ctx.arc(x + width - radius, y + radius, radius, Math.PI * 1.5, 0);
ctx.arc(x + width - radius, y + height - radius, radius, 0, Math.PI * 0.5);
ctx.arc(x + radius, y + height - radius, radius, Math.PI * 0.5, Math.PI);
ctx.closePath();
}
drawRoundedRect(ctx, 100, 100, 200, 150, 20);
ctx.fillStyle = '#9b59b6';
ctx.fill();
텍스트 렌더링
Canvas에 텍스트를 그리고 측정할 수 있습니다.
ctx.font = 'bold 48px "Segoe UI", sans-serif';
ctx.fillStyle = '#2c3e50';
ctx.fillText('Canvas Graphics', 150, 200);
const textWidth = ctx.measureText('Canvas Graphics').width;
ctx.strokeStyle = '#e74c3c';
ctx.strokeRect(150, 160, textWidth, 50);
그림자 효과
그림자 속성을 설정하여 깊이감을 표현할 수 있습니다.
ctx.shadowBlur = 15;
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
ctx.fillStyle = '#3498db';
ctx.fillRect(200, 200, 200, 100);
투명도와 합성 모드
globalAlpha와 globalCompositeOperation으로 고급 렌더링 효과를 구현할 수 있습니다.
// 100개의 반투명 원 그리기
ctx.globalAlpha = 0.6;
for (let i = 0; i < 100; i++) {
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
const radius = Math.random() * 40 + 10;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = `rgb(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})`;
ctx.fill();
}
// 합성 모드 예시
ctx.globalCompositeOperation = 'lighter';
ctx.globalAlpha = 1.0;
클리핑 영역
clip() 메서드로 특정 영역 밖의 그리기를 제한할 수 있습니다.
ctx.beginPath();
ctx.arc(400, 300, 150, 0, Math.PI * 2);
ctx.clip();
// 이후 그리기 작업은 원 영역 내로 제한됨
ctx.fillStyle = '#e74c3c';
ctx.fillRect(0, 0, 800, 600);
Non-zero Winding Rule
경로의 방향성에 따라 채우기 영역을 결정하는 규칙입니다.
ctx.beginPath();
// 시계 반대방향 (카운터 감소)
ctx.arc(400, 300, 200, 0, Math.PI * 2, true);
// 시계 방향 (카운터 증가)
ctx.arc(400, 300, 100, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = '#3498db';
ctx.fill();
마우스 이벤트 처리
isPointInPath()로 경로 내 클릭을 감지할 수 있습니다.
const circles = [];
for (let i = 0; i < 10; i++) {
circles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
radius: Math.random() * 50 + 20,
color: '#95a5a6'
});
}
function renderCircles() {
circles.forEach(circle => {
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2);
ctx.fillStyle = circle.color;
ctx.fill();
});
}
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const clickX = e.clientX - rect.left;
const clickY = e.clientY - rect.top;
circles.forEach((circle, index) => {
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2);
if (ctx.isPointInPath(clickX, clickY)) {
circle.color = '#e74c3c';
}
});
renderCircles();
});
renderCircles();
애니메이션 구현
requestAnimationFrame이나 setInterval로 부드러운 애니메이션을 만들 수 있습니다.
const balls = [];
const ballCount = 100;
class Ball {
constructor() {
this.r = Math.random() * 30 + 10;
this.x = Math.random() * (canvas.width - this.r * 2) + this.r;
this.y = Math.random() * (canvas.height - this.r * 2) + this.r;
this.vx = (Math.random() - 0.5) * 8;
this.vy = (Math.random() - 0.5) * 8;
this.color = `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, ${Math.random()})`;
}
update() {
if (this.x + this.r > canvas.width || this.x - this.r < 0) this.vx *= -1;
if (this.y + this.r > canvas.height || this.y - this.r < 0) this.vy *= -1;
this.x += this.vx;
this.y += this.vy;
}
draw(ctx) {
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
}
for (let i = 0; i < ballCount; i++) {
balls.push(new Ball());
}
function animate() {
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'lighter';
balls.forEach(ball => {
ball.update();
ball.draw(ctx);
});
requestAnimationFrame(animate);
}
animate();
라이브러리 활용
복잡한 차트나 그래프가 필요하다면 RGraph 같은 전문 라이브러리를 고려해보세요. RGraph는 Canvas 기반의 인터랙티브 차트 라이브러리로 바 차트, 파이 차트, 레이더 차트, 선 그래프 등 다양한 시각화를 지원합니다.