본문 바로가기

Canvas API 기초

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

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

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

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

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

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

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

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

DPR transform과 save/설정/draw/restore에서 context state와 bitmap/path의 변화가 다르다. restore 뒤 그린 픽셀이 남는 것을 동일 canvas/context/model로 추적한다. 의미 경로는 별도 병렬 관계로 위 관계도와 표에 유지한다.

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

FLOWCHART · WEB PLATFORM CONTRACT

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

하나의 장면 모델에서 픽셀 렌더와 접근성·좌표 판정을 나눠 관리한다. 픽셀 버퍼는 CSS 크기와 DPR에 맞추고 그리기 상태는 save/restore로 격리한다.

Canvas raster state와 semantic interaction의 병렬 경계같은 scene은 픽셀과 DOM·좌표 판정 경로에 병렬로 들어갑니다. 새 실습은 DPR transform을 기본 좌표로 먼저 정하고 save 뒤 색·선·폰트를 격리합니다. DOM 상태 설명은 구현하며 포커스 대안·별도 hit-test는 확장 설계입니다. save/restore 경계는 bitmap과 현재 path를 저장하는 경계가 아닙니다.픽셀 렌더 경로그리기 상태 경계같은 모델의 의미 경로장면 모델좌표·상태·ID버퍼 준비CSS 크기 × DPRDPR transform 지정크기 변경 뒤 재설정save / 설정색·선·폰트 지정save 뒤 draw·restore경로와 그리기beginPath·commandsfill / strokebitmap이미 그린 픽셀restore로 되돌림 아님DOM 설명동일 장면·상태 문장포커스 대안좌표 판정 모델같은 scene에서 계산추가 구현의 책임restore는 상태 복원path·bitmap 복원 아님
Canvas raster state와 semantic interaction의 병렬 경계같은 scene은 픽셀과 DOM·좌표 판정 경로에 병렬로 들어갑니다. 새 실습은 DPR transform을 기본 좌표로 먼저 정하고 save 뒤 색·선·폰트를 격리합니다. DOM 상태 설명은 구현하며 포커스 대안·별도 hit-test는 확장 설계입니다. save/restore 경계는 bitmap과 현재 path를 저장하는 경계가 아닙니다.픽셀 렌더 경로그리기 상태 경계같은 모델의 의미 경로장면 모델좌표·상태·ID버퍼 준비CSS 크기 × DPRDPR transform 지정크기 변경 뒤 재설정save / 설정색·선·폰트 지정save 뒤 draw·restore경로와 그리기beginPath·commandsfill / strokebitmap이미 그린 픽셀restore로 되돌림 아님DOM 설명동일 장면·상태 문장포커스 대안좌표 판정 모델같은 scene에서 계산추가 구현의 책임restore는 상태 복원path·bitmap 복원 아님

같은 scene은 픽셀과 DOM·좌표 판정 경로에 병렬로 들어갑니다. 새 실습은 DPR transform을 기본 좌표로 먼저 정하고 save 뒤 색·선·폰트를 격리합니다. DOM 상태 설명은 구현하며 포커스 대안·별도 hit-test는 확장 설계입니다. save/restore 경계는 bitmap과 현재 path를 저장하는 경계가 아닙니다.

Canvas bitmap은 시각 출력일 뿐 DOM 의미·focus·hit target을 만들지 않는다. 픽셀 렌더와 접근성 경로는 같은 모델을 병렬로 소비한다. save/restore는 현재 path와 이미 그린 bitmap을 저장하거나 되돌리지 않는다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
context 준비context: 저장된 state / bitmap: 기존 픽셀DPR buffer와 transform을 맞추고 save한다해상도와 논리 좌표를 구분한다
상태 변경과 drawcontext: 변경된 state / bitmap: 새 픽셀 있음스타일/transform을 바꿔 픽셀을 그린다context state와 실제 bitmap은 다른 자료다
restorecontext: 저장 state 복원 / bitmap/path: 그린 내용 자동 복원 안 됨저장한 context만 되돌린다restore를 undo 화면으로 오해하지 않게 한다

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

DPR transform과 save/설정/draw/restore에서 context state와 bitmap/path의 변화가 다르다. restore 뒤 그린 픽셀이 남는 것을 동일 canvas/context/model로 추적한다. 의미 경로는 별도 병렬 관계로 위 관계도와 표에 유지한다.

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

FLOWCHART · WEB PLATFORM CONTRACT

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

하나의 장면 모델에서 픽셀 렌더와 접근성·좌표 판정을 나눠 관리한다. 픽셀 버퍼는 CSS 크기와 DPR에 맞추고 그리기 상태는 save/restore로 격리한다.

Canvas raster state와 semantic interaction의 병렬 경계같은 scene은 픽셀과 DOM·좌표 판정 경로에 병렬로 들어갑니다. 새 실습은 DPR transform을 기본 좌표로 먼저 정하고 save 뒤 색·선·폰트를 격리합니다. DOM 상태 설명은 구현하며 포커스 대안·별도 hit-test는 확장 설계입니다. save/restore 경계는 bitmap과 현재 path를 저장하는 경계가 아닙니다.픽셀 렌더 경로그리기 상태 경계같은 모델의 의미 경로장면 모델좌표·상태·ID버퍼 준비CSS 크기 × DPRDPR transform 지정크기 변경 뒤 재설정save / 설정색·선·폰트 지정save 뒤 draw·restore경로와 그리기beginPath·commandsfill / strokebitmap이미 그린 픽셀restore로 되돌림 아님DOM 설명동일 장면·상태 문장포커스 대안좌표 판정 모델같은 scene에서 계산추가 구현의 책임restore는 상태 복원path·bitmap 복원 아님
Canvas raster state와 semantic interaction의 병렬 경계같은 scene은 픽셀과 DOM·좌표 판정 경로에 병렬로 들어갑니다. 새 실습은 DPR transform을 기본 좌표로 먼저 정하고 save 뒤 색·선·폰트를 격리합니다. DOM 상태 설명은 구현하며 포커스 대안·별도 hit-test는 확장 설계입니다. save/restore 경계는 bitmap과 현재 path를 저장하는 경계가 아닙니다.픽셀 렌더 경로그리기 상태 경계같은 모델의 의미 경로장면 모델좌표·상태·ID버퍼 준비CSS 크기 × DPRDPR transform 지정크기 변경 뒤 재설정save / 설정색·선·폰트 지정save 뒤 draw·restore경로와 그리기beginPath·commandsfill / strokebitmap이미 그린 픽셀restore로 되돌림 아님DOM 설명동일 장면·상태 문장포커스 대안좌표 판정 모델같은 scene에서 계산추가 구현의 책임restore는 상태 복원path·bitmap 복원 아님

같은 scene은 픽셀과 DOM·좌표 판정 경로에 병렬로 들어갑니다. 새 실습은 DPR transform을 기본 좌표로 먼저 정하고 save 뒤 색·선·폰트를 격리합니다. DOM 상태 설명은 구현하며 포커스 대안·별도 hit-test는 확장 설계입니다. save/restore 경계는 bitmap과 현재 path를 저장하는 경계가 아닙니다.

Canvas bitmap은 시각 출력일 뿐 DOM 의미·focus·hit target을 만들지 않는다. 픽셀 렌더와 접근성 경로는 같은 모델을 병렬로 소비한다. save/restore는 현재 path와 이미 그린 bitmap을 저장하거나 되돌리지 않는다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
context 준비context: 저장된 state / bitmap: 기존 픽셀DPR buffer와 transform을 맞추고 save한다해상도와 논리 좌표를 구분한다
상태 변경과 drawcontext: 변경된 state / bitmap: 새 픽셀 있음스타일/transform을 바꿔 픽셀을 그린다context state와 실제 bitmap은 다른 자료다
restorecontext: 저장 state 복원 / bitmap/path: 그린 내용 자동 복원 안 됨저장한 context만 되돌린다restore를 undo 화면으로 오해하지 않게 한다

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 애니메이션 프레임 순환 — 상태 추적

rAF callback에서 timestamp 차이→model 갱신→clear/render→다음 예약 여부가 변한다. 원본이 확장 설계라고 명시한 장면 모델을 고정하고 자동 반복/메모리 회수 오해를 피한다.

Canvas 애니메이션 프레임 순환

LOOP · WEB PLATFORM CONTRACT

Canvas 애니메이션 프레임 순환

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

Canvas 애니메이션 프레임 순환이 순환은 정적 원본의 확장 설계입니다. 실선은 콜백 처리·다음 예약, 점선은 모델 쓰기 또는 읽기입니다. rAF는 requestAnimationFrame입니다. 다음 예약 없이 콜백이 자동 반복된다고 가정하지 않습니다. 입력 수집입력 event 반영경과 시간timestamp 차이모델 갱신속도 × 경과 초clear이전 bitmap 지우기render현재 모델 읽고 그리기측정한 프레임의 비용다음 예약rAF 재호출장면 모델입력·update가 쓰기render는 읽기
Canvas 애니메이션 프레임 순환이 순환은 정적 원본의 확장 설계입니다. 실선은 콜백 처리·다음 예약, 점선은 모델 쓰기 또는 읽기입니다. rAF는 requestAnimationFrame입니다. 다음 예약 없이 콜백이 자동 반복된다고 가정하지 않습니다. 입력 수집입력 event 반영경과 시간timestamp 차이모델 갱신속도 × 경과 초clear이전 bitmap 지우기render현재 모델 읽고 그리기측정한 프레임의 비용다음 예약rAF 재호출장면 모델입력·update가 쓰기render는 읽기

이 순환은 정적 원본의 확장 설계입니다. 실선은 콜백 처리·다음 예약, 점선은 모델 쓰기 또는 읽기입니다. rAF는 requestAnimationFrame입니다. 다음 예약 없이 콜백이 자동 반복된다고 가정하지 않습니다.

requestAnimationFrame 콜백마다 경과 시간으로 모델을 갱신하고 bitmap을 다시 그린다. 점선은 모델 접근이다. 입력·update가 모델을 바꾸고 render는 읽기만 한다. 측정 후 requestAnimationFrame을 다시 호출해야 다음 콜백이 예약된다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
  • 원래 본문의 구현과 도표가 설명한 설계 보강을 분리한다. 서사에 포함한 확장 동작은 assumptions에 명시하고 이미 구현된 안전장치로 주장하지 않는다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
frame 시작입력: timestamp 차이 / model: 현재 위치/상태rAF timestamp 차이로 model을 갱신한다확장 animation 모델로 시간을 계산한다
그리기bitmap: 새 frame / model: 현재 값canvas를 clear하고 현재 model을 렌더한다bitmap과 모델의 역할을 구분한다
다음 예약예약: 다음 rAF / 콜백: 한 번 실행되는 예약측정 뒤 다음 frame을 요청한다현재 rAF 콜백은 한 번만 실행되므로 다음 프레임을 받으려면 requestAnimationFrame을 다시 호출해 예약해야 한다

Canvas 애니메이션 프레임 순환 — 상태 추적

rAF callback에서 timestamp 차이→model 갱신→clear/render→다음 예약 여부가 변한다. 원본이 확장 설계라고 명시한 장면 모델을 고정하고 자동 반복/메모리 회수 오해를 피한다.

Canvas 애니메이션 프레임 순환

LOOP · WEB PLATFORM CONTRACT

Canvas 애니메이션 프레임 순환

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

Canvas 애니메이션 프레임 순환이 순환은 정적 원본의 확장 설계입니다. 실선은 콜백 처리·다음 예약, 점선은 모델 쓰기 또는 읽기입니다. rAF는 requestAnimationFrame입니다. 다음 예약 없이 콜백이 자동 반복된다고 가정하지 않습니다. 입력 수집입력 event 반영경과 시간timestamp 차이모델 갱신속도 × 경과 초clear이전 bitmap 지우기render현재 모델 읽고 그리기측정한 프레임의 비용다음 예약rAF 재호출장면 모델입력·update가 쓰기render는 읽기
Canvas 애니메이션 프레임 순환이 순환은 정적 원본의 확장 설계입니다. 실선은 콜백 처리·다음 예약, 점선은 모델 쓰기 또는 읽기입니다. rAF는 requestAnimationFrame입니다. 다음 예약 없이 콜백이 자동 반복된다고 가정하지 않습니다. 입력 수집입력 event 반영경과 시간timestamp 차이모델 갱신속도 × 경과 초clear이전 bitmap 지우기render현재 모델 읽고 그리기측정한 프레임의 비용다음 예약rAF 재호출장면 모델입력·update가 쓰기render는 읽기

이 순환은 정적 원본의 확장 설계입니다. 실선은 콜백 처리·다음 예약, 점선은 모델 쓰기 또는 읽기입니다. rAF는 requestAnimationFrame입니다. 다음 예약 없이 콜백이 자동 반복된다고 가정하지 않습니다.

requestAnimationFrame 콜백마다 경과 시간으로 모델을 갱신하고 bitmap을 다시 그린다. 점선은 모델 접근이다. 입력·update가 모델을 바꾸고 render는 읽기만 한다. 측정 후 requestAnimationFrame을 다시 호출해야 다음 콜백이 예약된다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
  • 원래 본문의 구현과 도표가 설명한 설계 보강을 분리한다. 서사에 포함한 확장 동작은 assumptions에 명시하고 이미 구현된 안전장치로 주장하지 않는다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
frame 시작입력: timestamp 차이 / model: 현재 위치/상태rAF timestamp 차이로 model을 갱신한다확장 animation 모델로 시간을 계산한다
그리기bitmap: 새 frame / model: 현재 값canvas를 clear하고 현재 model을 렌더한다bitmap과 모델의 역할을 구분한다
다음 예약예약: 다음 rAF / 콜백: 한 번 실행되는 예약측정 뒤 다음 frame을 요청한다현재 rAF 콜백은 한 번만 실행되므로 다음 프레임을 받으려면 requestAnimationFrame을 다시 호출해 예약해야 한다

Canvas API 마무리

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

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

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

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

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

앞의 500×300 예제는 고정 크기의 좌표 연습입니다. 320px 화면에 그대로 놓으면 캔버스와 테두리가 넘칩니다. 다음 별도 파일은 320px 이상 화면에서 글자를 작게 축소하는 대신 사용 가능한 가로폭에서 선·원·글자의 위치를 다시 계산합니다. HTML에는 <canvas id="responsive-canvas">가운데 원과 가로선을 그리는 연습 그림입니다.</canvas><p id="canvas-status"></p>을 넣습니다. 원래 canvas-script.js와 새 파일을 동시에 연결하지 않습니다.

새 Canvas 실습의 화면 여백
body { margin: 0; padding: 16px; }
#responsive-canvas { display: block; outline: 1px solid #777; }
#canvas-status { overflow-wrap: anywhere; }
CSS 좌표와 픽셀 버퍼를 분리하는 좁은 화면 Canvas
const canvas = document.getElementById('responsive-canvas');
const status = document.getElementById('canvas-status');
const ctx = canvas.getContext('2d');

function drawResponsiveCanvas() {
    if (!ctx) { status.textContent = '2D 그리기를 사용할 수 없습니다.'; return; }
    const width = Math.min(500, document.documentElement.clientWidth - 32);
    const height = 300;
    const dpr = window.devicePixelRatio || 1;
    canvas.style.width = `${width}px`;
    canvas.style.height = `${height}px`;
    canvas.width = Math.round(width * dpr);
    canvas.height = Math.round(height * dpr);
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    ctx.clearRect(0, 0, width, height);

    const scene = { x: width / 2, y: 200, radius: 40 };
    ctx.save();
    ctx.strokeStyle = '#166534';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(24, 100);
    ctx.lineTo(width - 24, 100);
    ctx.stroke();
    ctx.beginPath();
    ctx.arc(scene.x, scene.y, scene.radius, 0, Math.PI * 2);
    ctx.stroke();
    ctx.fillStyle = '#111827';
    ctx.font = '20px sans-serif';
    ctx.fillText('Canvas 연습', 24, 48);
    ctx.restore();
    status.textContent = `원 중심 (${scene.x}, ${scene.y}), 반지름 ${scene.radius}; CSS ${width}×${height}, 버퍼 ${canvas.width}×${canvas.height}`;
}

drawResponsiveCanvas();
window.addEventListener('resize', drawResponsiveCanvas);

320px 화면·DPR 2라면 CSS 폭은 288, 버퍼는 576×600이고 원의 CSS 중심은 (144, 200)입니다. 375px에서는 CSS 폭 343, 같은 DPR 2의 버퍼 폭은 686입니다. 그릴 때는 CSS 좌표를 쓰고 transform이 버퍼 좌표로 바꿉니다. 글자의 CSS 크기는 계속 20px입니다. canvas.width를 원 중심 계산에 사용하면 DPR을 두 번 적용하게 되므로 width와 구별합니다. 창 크기를 바꾸어 width/height 속성을 다시 설정하면 Canvas의 bitmap·그리기 상태가 초기화되므로 transform과 스타일을 다시 지정하고 그립니다. 그림 내용은 같은 scene에서 DOM 문장으로도 설명했습니다.

예시: 다음 프레임은 스스로 예약합니다

프레임 순환 그림은 앞의 정적 예제에 대한 확장입니다. 다음 함수는 픽셀을 그리지 않고, 시간 차이로 모델 위치가 변하는 계산만 떼어 놓은 것입니다. 브라우저 애니메이션에서는 requestAnimationFrame의 콜백에서 계산 → clear → draw를 수행하고 다음 콜백을 다시 예약합니다. 콜백 시각 간격이 매번 같다는 가정은 하지 않습니다.

경과 시간을 초로 바꿔 모델 위치를 갱신하기
function nextPosition(x, previousMs, nowMs, speed) {
    const seconds = Math.max(0, nowMs - previousMs) / 1000;
    return x + speed * seconds;
}
console.log(nextPosition(10, 1000, 1250, 40)); // 20

독립 연습

뷰포트 폭 375, 여백은 양쪽 16, 높이 300, DPR 3인 경우 CSS 크기·버퍼 크기·원의 CSS 중심을 계산하세요. 별도의 이동 모델은 x 10, 이전 시각 1000ms에서 1500ms까지 초당 40px로 움직였습니다. 다음 x는 얼마일까요? restore를 호출하면 이 이동이나 이미 그린 픽셀이 이전 상태로 돌아가는지도 판단하세요.

좌표와 시간의 단위를 구별하는 풀이

CSS 크기는 343×300, 버퍼는 1029×900, 원의 CSS 중심은 (171.5, 200)입니다. 500ms는 0.5초이므로 x는 10+40×0.5=30입니다. restore는 transform·style 같은 그리기 상태를 복원하며 모델 위치나 이미 그린 bitmap을 되돌리지 않습니다. 새 프레임에서 픽셀을 지우고 현재 모델을 다시 그리는 이유입니다.