안동민 개발노트

본문 시작

Canvas API 기초

canvas의 2D 컨텍스트와 좌표계를 준비하고 선·사각형·원·텍스트·경로 상태를 픽셀 화면에 그립니다.

우리는 9장에서 Fetch API로 서버와 통신하고, 로컬/세션 스토리지로 클라이언트 데이터를 관리하며, History API로 SPA 라우팅을 구현하는 흐름을 학습했습니다.

이번 절에서는 웹 페이지를 더 시각적이고 인터랙티브하게 만드는 핵심 도구인 Canvas API를 다룹니다.

Canvas API는 HTML5에 도입된 <canvas> 요소를 이용해 웹 페이지에서 2D 그래픽을 직접 그릴 수 있게 해주는 JavaScript API입니다.

픽셀 단위 이미지 조작, 도형/텍스트 렌더링, 애니메이션 구현이 가능해 정적 이미지로 표현하기 어려운 동적 시각화를 만들 때 유용합니다.

간단한 게임, 이미지 편집기, 데이터 시각화(차트 등) 같은 다양한 분야에서 활용됩니다.

이번 절에서는 캔버스 요소 준비, 컨텍스트 획득, 선/사각형/원/텍스트 렌더링까지 기본 사용 흐름을 순서대로 정리하겠습니다.

Canvas 렌더 상태와 픽셀 파이프라인

FLOWCHART · WEB PLATFORM CONTRACT

Canvas 렌더 상태와 픽셀 파이프라인

같은 scene model에서 픽셀 렌더와 접근성·hit-test 모델을 병렬로 관리한다.

Canvas raster state와 semantic interaction의 병렬 경계 공유 scene model에서 픽셀 렌더와 접근성 및 hit-test 책임이 갈라진다. Canvas는 DPR과 backing store를 맞춘 뒤 그리기 상태를 저장하고 path를 그린다. 현재 path와 bitmap은 save/restore 대상이 아니다. 공유 scene modelsemantic ID · geometry그리기·상호작용의 기준CSS 크기 × DPRbacking store 크기 설정resize 시 상태·bitmap 초기화save()DPR transform · styleclip 경계 설정beginPath → fill / stroke경로 구성 → 픽셀 그리기restore로 그리기 상태 복원Canvas bitmap픽셀 출력DOM 의미는 생성되지 않음접근성·hit-test model같은 ID와 scene state 사용DOM button · list · status픽셀 렌더와 접근성은 같은 모델에서 갈라지는 별도의 책임이다.save/restore는 현재 path와 bitmap을 저장하거나 복원하지 않는다.

픽셀 렌더 경로

  1. backing store

    CSS size×DPR로 canvas width/height를 맞춘다. resize는 context state를 reset한다.

  2. state boundary

    save() 뒤 DPR transform·style·clip을 설정한다.

  3. path와 bitmap

    beginPath와 commands 뒤 fill/stroke로 pixels를 commit하고 restore()로 다음 draw와 격리한다.

병렬 semantic 경로

같은 scene state와 semantic ID에서 pointer 좌표용 hit-test model과 button·list·table·status 같은 DOM 접근 대안을 별도로 유지한다. bitmap 뒤에 직렬로 붙는 단계가 아니다.

Canvas bitmap은 시각 출력일 뿐 DOM 의미·focus·hit target을 만들지 않는다. raster와 접근성 경로가 같은 scene model을 병렬로 소비해야 한다.


Canvas API 개요 및 <canvas> 요소

Canvas는 말 그대로 도화지와 같습니다.

HTML 문서에는 <canvas>라는 빈 도화지 요소를 배치하고, 실제 그림은 JavaScript 코드를 사용하여 이 도화지 위에 그리는 방식입니다.

<canvas> 요소 준비

HTML 문서에 <canvas> 태그를 추가하여 그림을 그릴 공간을 정의합니다.

width와 height 속성으로 캔버스의 크기를 지정할 수 있습니다.

(CSS로 크기를 조절할 수도 있지만, 렌더링 품질에 영향을 줄 수 있으므로 HTML 속성으로 고정 크기를 지정하는 것이 좋습니다.)

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas API 기초</title>
    <style>
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background-color: #f0f0f0;
            margin: 0;
        }
        canvas {
            border: 2px solid #333; /* 캔버스 경계를 시각적으로 보여주기 위함 */
            background-color: #fff;
        }
    </style>
</head>
<body>
    <canvas id="myCanvas" width="500" height="300">
        이 브라우저는 Canvas를 지원하지 않습니다.
    </canvas>

    <script src="canvas-script.js"></script>
</body>
</html>

렌더링 컨텍스트 가져오기

<canvas> 요소 자체는 그림을 그릴 수 있는 인터페이스를 직접 제공하지 않습니다.

그림을 그리기 위해서는 렌더링 컨텍스트(Rendering Context)를 얻어야 합니다.

가장 흔히 사용되는 것은 2D 그래픽을 위한 2d 컨텍스트입니다.

// canvas-script.js
const canvas = document.getElementById('myCanvas');

// getContext() 메서드를 사용하여 렌더링 컨텍스트를 가져옵니다.
const ctx = canvas.getContext('2d');

// 캔버스가 정상적으로 초기화되었는지 확인
if (ctx) {
    console.log("2D 렌더링 컨텍스트를 성공적으로 가져왔습니다.");
    // 이제 ctx 객체를 사용하여 그림을 그릴 수 있습니다.
} else {
    console.error("이 브라우저는 Canvas 2D 컨텍스트를 지원하지 않습니다.");
}

ctx (Context) 객체는 캔버스에 도형, 텍스트, 이미지 등을 그리는 데 필요한 모든 메서드와 속성을 가지고 있습니다.

캔버스는 CSS 크기와 실제 픽셀 버퍼 크기를 따로 다루므로, 고해상도 화면에서는 devicePixelRatio까지 함께 고려해야 선과 글자가 흐려지지 않습니다.

앞의 Canvas 렌더 상태와 픽셀 파이프라인 다이어그램은 "보이는 크기"와 실제 픽셀 좌표계를 나눠 확인하는 기준입니다.


기본적인 도형 그리기

Canvas에서 그림을 그리는 과정은 보통 다음과 같습니다.

경로 시작: ctx.beginPath()로 새로운 그림 경로를 시작합니다.

경로 정의: moveTo(), lineTo(), arc(), rect() 등으로 경로를 정의합니다.

(선택) 경로 닫기: 닫힌 윤곽선이 필요하면 그리기 전에 closePath()를 호출합니다.

그림 그리기: stroke()로 선을 그리거나 fill()로 채웁니다. fill()은 열린 하위 경로도 닫힌 것으로 처리합니다.

선(Lines) 그리기

// 선의 색상 설정
ctx.strokeStyle = 'blue';
// 선의 두께 설정
ctx.lineWidth = 5;

// 첫 번째 선: (50, 50)에서 (200, 50)까지
ctx.beginPath();       // 새로운 경로 시작
ctx.moveTo(50, 50);    // 시작점 이동
ctx.lineTo(200, 50);   // (200, 50)까지 선 그리기
ctx.stroke();          // 경로를 따라 선 그리기 (stroke)

// 두 번째 선: (200, 50)에서 (200, 150)까지
// beginPath()를 다시 호출하면 이전 경로와 독립적으로 그림
ctx.beginPath();
ctx.moveTo(200, 50);
ctx.lineTo(200, 150);
ctx.stroke();

// 삼각형 그리기
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(300, 100);
ctx.lineTo(350, 200);
ctx.lineTo(250, 200);
ctx.closePath(); // 시작점으로 경로를 닫아 삼각형 완성
ctx.stroke();

사각형(Rectangles) 그리기

사각형을 그리는 전용 메서드가 있습니다.

  • ctx.rect(x, y, width, height): 사각형 경로를 정의
  • ctx.strokeRect(x, y, width, height): 윤곽선 사각형 그리기
  • ctx.fillRect(x, y, width, height): 채워진 사각형 그리기
  • ctx.clearRect(x, y, width, height): 지정된 영역을 투명하게 지우기
// 채워진 사각형 (fillStyle 설정)
ctx.fillStyle = 'green'; // 채울 색상 설정
ctx.fillRect(50, 100, 100, 50); // (x, y, width, height)

// 윤곽선 사각형 (strokeStyle, lineWidth 설정)
ctx.strokeStyle = 'purple';
ctx.lineWidth = 3;
ctx.strokeRect(200, 100, 100, 50);

// 특정 영역 지우기
ctx.clearRect(70, 110, 60, 30); // 초록색 사각형의 일부를 지움

원(Arcs/Circles) 그리기

arc() 메서드를 사용하여 원 또는 호를 그립니다.

  • ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise)
    • x, y: 원의 중심 좌표
    • radius: 반지름
    • startAngle, endAngle: 호의 시작 및 끝 각도 (라디안 단위)
    • counterclockwise: true면 반시계 방향, false면 시계 방향 (기본값)
// 라디안 계산: Math.PI는 파이 (180도)
// 전체 원: 0부터 2 * Math.PI
// 반원: 0부터 Math.PI

// 채워진 원
ctx.fillStyle = 'orange';
ctx.beginPath();
ctx.arc(150, 230, 40, 0, 2 * Math.PI, false); // (x, y, 반지름, 시작각, 끝각, 반시계방향)
ctx.fill();

// 윤곽선 원
ctx.strokeStyle = 'darkblue';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(350, 230, 40, 0, 2 * Math.PI, false);
ctx.stroke();

// 반원 (파이차트 조각처럼)
ctx.fillStyle = 'lightblue';
ctx.beginPath();
ctx.moveTo(400, 50); // 중심점에서 시작
ctx.arc(400, 50, 40, 0, Math.PI, false); // 아래쪽 반원 (기본 canvas 좌표계)
ctx.closePath(); // 경로를 명시적으로 닫음 (fill은 열린 경로도 암묵적으로 닫아 채움)
ctx.fill();

텍스트(Text) 그리기

텍스트를 그리는 메서드와 스타일 속성이 있습니다.

  • ctx.fillText(text, x, y, maxWidth): 채워진 텍스트 그리기
  • ctx.strokeText(text, x, y, maxWidth): 윤곽선 텍스트 그리기

텍스트 스타일 속성

  • ctx.font: 폰트 스타일 (예: '48px serif')
  • ctx.textAlign: 텍스트 정렬 ('left', 'right', 'center', 'start', 'end')
  • ctx.textBaseline: 텍스트 기준선 ('alphabetic', 'top', 'hanging', 'middle', 'ideographic', 'bottom')
ctx.fillStyle = 'black';
ctx.font = '30px Arial'; // 폰트 크기와 패밀리
ctx.textAlign = 'center'; // 수평 정렬
ctx.textBaseline = 'middle'; // 수직 정렬

// 캔버스 중앙에 텍스트 그리기
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;

ctx.fillText('Hello Canvas!', centerX, centerY);

ctx.strokeStyle = 'gray';
ctx.lineWidth = 1;
ctx.font = '20px Sans-serif';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.strokeText('Web Graphics', 10, 10);

그리기 상태 저장 및 복원

캔버스 컨텍스트의 현재 그리기 상태(색상, 선 두께, 변형 등)를 저장하고 나중에 복원할 수 있습니다.

이는 복잡한 그림을 그릴 때 매우 유용합니다.

  • ctx.save(): 현재 그리기 상태를 스택에 저장합니다.
  • ctx.restore(): 스택의 가장 최근 상태를 복원합니다.
// 채우기 색상: 빨간색
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 50, 50); // (0,0)에 빨간색 사각형

ctx.save(); // 현재 그리기 상태 저장 (채우기 색상: 빨간색)

ctx.fillStyle = 'blue'; // 새로운 상태: 파란색
ctx.fillRect(60, 0, 50, 50); // (60,0)에 파란색 사각형

ctx.save(); // 현재 그리기 상태 저장 (채우기 색상: 파란색)

ctx.fillStyle = 'green'; // 새로운 상태: 초록색
ctx.fillRect(120, 0, 50, 50); // (120,0)에 초록색 사각형

ctx.restore(); // 스택에서 가장 최근 상태 복원 (파란색)
ctx.fillRect(180, 0, 50, 50); // (180,0)에 파란색 사각형

ctx.restore(); // 스택에서 다음 상태 복원 (빨간색)
ctx.fillRect(240, 0, 50, 50); // (240,0)에 빨간색 사각형

save()와 restore()는 색상·선 스타일·그림자·변형·클리핑 영역 등의 그리기 상태를 저장하고 복원합니다. 현재 경로와 이미 그린 bitmap은 포함되지 않습니다.


상태 저장과 복원은 중첩될수록 흐름이 헷갈리기 쉽습니다.

save()와 restore()는 작은 그리기 단위마다 색상, 좌표 변환, 선 스타일을 복원해 상태가 의도치 않게 번지는 일을 막습니다.


Canvas API 활용 분야

  • 게임 개발: 2D 게임 엔진이나 캐주얼 게임 개발에 널리 사용됩니다.
  • 데이터 시각화: 차트(막대 그래프, 파이 차트, 선 그래프 등)나 복잡한 통계 데이터를 시각적으로 표현하는 데 활용됩니다 (예: Chart.js).
  • 이미지 편집: 이미지 필터 적용, 크롭, 회전, 그림 그리기 등의 기능 구현.
  • 애니메이션: 프레임 단위로 그림을 지우고 다시 그리면서 동적인 애니메이션 효과를 만듭니다.
  • 사용자 정의 UI 컴포넌트: SVG만으로는 부족한 복잡한 그래픽이 필요할 때 사용됩니다.
Canvas animation frame loop

LOOP · WEB PLATFORM CONTRACT

Canvas animation frame loop

입력·경과 시간·상태 갱신 뒤 bitmap을 지우고 현재 모델을 그린다. 모델 쓰기와 읽기를 구분하고 다음 frame을 예약한다.

Canvas animation frame loop input → elapsed time → state update → clear → render → measure가 shared model hub에 write-back하고 다음 frame으로 돌아가는 loop 입력 수집 입력 → model 경과 시간 시간 차이 상태 갱신 delta로 갱신 clear 이전 bitmap 제거 render model 렌더 측정 다음 frame 판단 공유 모델 frame 사이의 단일 상태
  1. 입력 수집

    event를 model에 반영

  2. 경과 시간

    timestamp 차이

  3. 상태 갱신

    delta 기반 update

  4. clear

    이전 bitmap 제거

  5. render

    현재 model 그리기

  6. 측정

    다음 frame 판단

requestAnimationFrame 콜백마다 경과 시간으로 모델을 갱신하고 bitmap을 다시 그린다. render는 모델을 읽고 입력·update가 모델을 바꾼다.


Canvas API 마무리

이번 장에서는 HTML5에 도입된 웹 그래픽 도구인 Canvas API의 기초를 학습했습니다.

<canvas> 요소와 getContext('2d')를 사용하면 JavaScript로 2D 렌더링 컨텍스트를 다룰 수 있습니다.

선, 사각형, 원, 텍스트를 그리는 기본 메서드와 save()/restore()를 이용한 상태 관리 흐름을 함께 확인했습니다.

Canvas API는 정적인 이미지로 표현하기 어려운 동적 그래픽을 직접 제어할 때 사용합니다.

웹 게임, 데이터 시각화, 인터랙티브 아트처럼 픽셀 단위 제어가 필요한 화면에서는 좌표계, 렌더링 순서, 다시 그리기 비용을 함께 고려해야 합니다.