본문 바로가기

SVG와 캔버스 기초

DOM으로 다루는 벡터 SVG와 픽셀을 그리는 Canvas를 비교하고 2D 컨텍스트로 간단한 애니메이션을 만듭니다.

지금까지 우리는 HTML을 사용하여 웹 페이지의 구조를 만들고, 텍스트, 링크, 일반 이미지(<img>), 그리고 비디오/오디오와 같은 멀티미디어 콘텐츠를 삽입하는 방법을 학습했습니다.

웹 그래픽을 다루는 두 방식인 SVG와 Canvas를 비교합니다.

HTML5는 웹 표준만으로도 복잡하고 동적인 그래픽을 웹 페이지에 구현할 수 있는 길을 열었습니다.

이 장에서는 벡터 기반 SVG(Scalable Vector Graphics)와 픽셀 기반 Canvas의 기본 개념, 장단점, 사용법을 정리합니다.

이 두 기술은 웹 애플리케이션에서 다이어그램, 차트, 게임, 인터랙티브 애니메이션 등 다양한 시각 요소를 구현하는 데 필수적인 기반이 됩니다.

SVG는 도형 노드, Canvas는 그려진 픽셀을 유지합니다

SVG & CANVAS

SVG는 도형 노드, Canvas는 그려진 픽셀을 유지합니다

같은 원을 그려도 개별 객체의 상태·이벤트·접근성 정보를 관리하는 방식이 다릅니다.

SVG 노드와 Canvas 픽셀이 남는 과정SVG에서는 circle DOM 노드의 속성을 변경하고 화면이 다시 표시되어도 노드가 남는다. Canvas에서는 앱이 원 상태를 보유하고 arc와 fill을 호출해 비트맵에 픽셀을 기록한다. 원마다 DOM 노드가 생기지 않는다. 두 경로는 성능 순위를 뜻하지 않는다.SVG · 도형별 DOM을 유지Canvas · 원의 상태는 앱이 유지속성 변경다시 표시API 호출픽셀 기록circle 노드cx · cy · rDOM 갱신CSS·이벤트 연결원의 화면노드가 남음앱의 상태x · y · r선택·클릭 판정그리기 호출arc · fill필요 시 재그림비트맵픽셀이 남음원 노드 없음
좁은 화면의 같은 두 렌더링 경로위 SVG 영역은 circle 노드, DOM 갱신, 원의 화면으로 이어지고 도형 노드를 유지한다. 아래 Canvas 영역은 앱 상태, 그리기 호출, 비트맵으로 이어지고 원의 상태와 클릭 판정은 앱이 관리한다. 각각 두 간선이며 두 영역 사이에 실행 순서는 없다.SVG · 도형별 DOM 유지Canvas · 앱이 도형 상태 유지속성 변경다시 표시API 호출픽셀 기록circle 노드cx · cy · rDOM 갱신CSS·이벤트 연결원의 화면노드가 남음앱의 상태x · y · r선택·클릭 판정그리기 호출arc · fill필요 시 재그림비트맵픽셀 · 원 노드 없음

SVG · 도형 노드를 유지

<svg viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="20" />
</svg>

원에 해당하는 DOM 노드가 남습니다. 속성·CSS·이벤트로 개별 도형을 다룰 수 있습니다. 보조 기술에 전달할 이름·의미와 조작은 목적에 맞게 지정합니다.

Canvas · 그린 픽셀을 유지

ctx.beginPath();
ctx.arc(50, 50, 20, 0, Math.PI * 2);
ctx.fill();

그리기 호출 뒤 비트맵의 픽셀이 남습니다. 원의 위치·선택 상태·클릭 판정은 앱이 관리합니다. 원마다 DOM 노드가 자동 생성되지 않습니다.

무엇을 개별적으로 다루는가?

도형별 스타일·이벤트·선택을 주로 다루면 SVG를 검토합니다. 픽셀 읽기·이미지 처리·빈번한 대량 그리기라면 Canvas를 검토합니다. 실제 성능은 작업량과 구현을 측정합니다.

크기와 접근성은 별도로 설계

SVG의 viewBox는 좌표와 확대를 연결합니다. Canvas는 CSS 표시 크기와 버퍼 크기를 DPR에 맞춰 구분합니다. Canvas 객체의 설명·조작에는 의미 있는 HTML 대안 등이 필요합니다.

두 모델이 모두 필요한 경우 Canvas 화면 위에 HTML/SVG 조작 요소를 조합할 수 있습니다. SVG도 이름·초점·조작을 자동으로 모두 해결하지 않으며, Canvas도 항상 전체 화면을 다시 그려야 하는 것은 아닙니다.


SVG: 확장 가능한 벡터 그래픽

SVG는 XML 기반의 벡터 그래픽을 웹에 표현하기 위한 마크업 언어입니다.

벡터 그래픽이라는 것은 그림을 점, 선, 면 등의 수학적인 형태로 정의한다는 의미입니다.

JPG, PNG와 같은 비트맵 그래픽(픽셀 기반)과는 근본적으로 다릅니다.

SVG의 특징 및 장점

확장성 (Scalable): 가장 큰 특징이자 장점입니다.

점·선·면으로 적은 벡터 도형은 표시 크기에 맞춰 다시 그릴 수 있습니다. SVG 안에 별도로 포함한 비트맵 사진까지 해상도가 독립적으로 바뀌는 것은 아닙니다.

로고, 아이콘, 다이어그램 등 다양한 해상도의 기기에서 선명하게 보여야 하는 그래픽에 매우 적합합니다.

작은 파일 크기: 복잡하지 않은 벡터 그래픽의 경우, 비트맵 이미지보다 파일 크기가 훨씬 작아 로딩 속도를 개선할 수 있습니다.

편집 용이성: SVG 코드는 XML 기반의 텍스트이므로, 텍스트 편집기나 SVG 편집 소프트웨어에서 쉽게 수정할 수 있습니다.

또한 CSS와 자바스크립트를 사용하여 색상, 크기, 위치 등을 동적으로 제어하고 애니메이션을 적용할 수 있습니다.

웹 접근성: SVG 요소는 의미론적인 정보(예: <title>, <desc>)를 포함할 수 있어, 스크린 리더와 같은 보조 기술이 그래픽의 내용을 이해하는 데 도움을 줍니다.

검색 가능: SVG는 텍스트 기반이므로, 검색 엔진이 이미지 내부의 텍스트를 인식할 수 있어 SEO에 유리할 수 있습니다.

SVG 기본 문법과 활용

SVG는 HTML 문서 내에 <svg> 태그를 사용하여 직접 삽입하거나, .svg 파일로 저장하여 <img> 태그나 CSS의 background-image 속성을 통해 참조할 수 있습니다.

여기서는 HTML 내에 직접 삽입하는 방식을 위주로 살펴봅니다.

  • <svg> 태그: SVG 그래픽을 그릴 캔버스 역할을 합니다. width와 height 속성으로 영역을 지정합니다.

    <svg width="200" height="200">
    </svg>
  • 기본 도형 그리기
    • <rect> (직사각형): x, y, width, height, fill, stroke, stroke-width 등의 속성을 사용합니다.
      <svg width="200" height="100">
          <rect x="10" y="10" width="80" height="80"
                fill="blue" stroke="black" stroke-width="3" />
      </svg>
    • <circle> (원): cx, cy (중심점 좌표), r (반지름), fill, stroke 등의 속성을 사용합니다.
      <svg width="200" height="100">
          <circle cx="50" cy="50" r="40" fill="red" stroke="purple" stroke-width="2" />
      </svg>
    • <line> (선): x1, y1 (시작점), x2, y2 (끝점), stroke, stroke-width 등의 속성을 사용합니다.
      <svg width="200" height="100">
          <line x1="10" y1="10" x2="190" y2="90" stroke="green" stroke-width="4" />
      </svg>
    • <polygon> (다각형): points 속성에 점들의 좌표를 공백으로 구분하여 나열합니다.
      <svg width="200" height="200">
          <polygon points="100,10 190,90 10,90" fill="orange" stroke="brown" stroke-width="2" />
      </svg>
    • <text> (텍스트): x, y (텍스트 시작 좌표), fill, font-size 등의 속성을 사용합니다.
      <svg width="200" height="100">
          <text x="10" y="50" fill="darkblue" font-size="20">Hello SVG!</text>
      </svg>
  • CSS와 자바스크립트를 이용한 제어: SVG 요소는 HTML 요소와 마찬가지로 CSS로 스타일링하거나 자바스크립트로 조작할 수 있습니다.

    <style>
        .my-circle {
            fill: #ffd700; /* 금색 */
            stroke: #cc0000;
            stroke-width: 5;
            transition: fill 0.3s ease; /* 호버 시 색상 변화 애니메이션 */
        }
        .my-circle:hover {
            fill: #ff4500; /* 오렌지색 */
        }
    </style>
    
    <svg width="100" height="100">
        <circle cx="50" cy="50" r="40" class="my-circle" id="interactiveCircle" />
    </svg>
    
    <script>
        const circle = document.getElementById('interactiveCircle');
        circle.addEventListener('click', () => {
            alert('SVG 원을 클릭했습니다!');
            circle.setAttribute('r', '50'); // 반지름 변경
        });
    </script>

SVG는 문서 안에서 요소 단위로 남으므로 개별 노드를 선택하고 조작할 수 있습니다.


Canvas: 비트맵 그래픽 그리기

Canvas는 HTML5에서 도입된 <canvas> 태그와 자바스크립트 API를 사용하여 비트맵(픽셀) 기반의 2D 그래픽을 웹 페이지에 직접 그릴 수 있도록 하는 기술입니다.

캔버스라는 이름처럼, 마치 그림을 그리는 도화지와 같습니다.

Canvas의 특징 및 장점

픽셀 기반: 이미지를 픽셀 단위로 직접 조작합니다.

복잡한 그래픽, 이미지 필터, 게임 그래픽 등 픽셀 단위의 정교한 제어가 필요할 때 적합합니다.

자바스크립트 제어: 모든 그리기는 자바스크립트 코드를 통해 이루어집니다.

이를 통해 매우 동적이고 인터랙티브한 그래픽을 생성할 수 있습니다.

반복 그리기: 많은 픽셀이나 도형을 반복 갱신하는 장면에 활용할 수 있습니다. 실제 성능은 장면·해상도·그리는 양·브라우저 구현에 따라 달라지므로 Canvas라는 이유만으로 항상 빠르다고 판단하지 않습니다.

다양한 활용: 차트, 그래프, 사진 편집기, 게임, 웹 기반 드로잉 툴 등 다양한 웹 애플리케이션에서 활용됩니다.

Canvas는 화면에 객체를 쌓아 두는 방식이 아니라, 현재 상태를 바탕으로 픽셀 결과를 계속 다시 그리는 방식으로 이해하면 쉽습니다.

Canvas 기본 문법과 활용

  • <canvas> 태그: 그림을 그릴 영역을 정의합니다. width와 height 속성으로 캔버스의 크기를 지정합니다.

    <canvas id="myCanvas" width="400" height="200" style="border:1px solid #000;"></canvas>
    • <canvas> 태그 자체는 비어 있습니다. 모든 그리기 작업은 자바스크립트를 통해 이루어집니다.
    • style 속성으로 테두리나 CSS 표시 크기를 바꿀 수 있습니다. 그리기 영역과 좌표 크기는 width·height 속성으로 정하며, CSS 표시 크기를 다르게 지정하면 내부 비트맵이 그 크기에 맞춰 확대되거나 축소됩니다.
  • 자바스크립트를 이용한 그리기

    캔버스 요소 가져오기: document.getElementById()를 사용하여 <canvas> 요소를 가져옵니다.

    렌더링 컨텍스트 얻기: 캔버스에 그림을 그리기 위한 컨텍스트 객체를 얻습니다.

    2D 그래픽의 경우 getContext('2d')를 사용합니다.

    그리기 명령 실행: 컨텍스트 객체의 메서드를 사용하여 도형, 텍스트, 이미지 등을 그립니다.

    <canvas id="myDrawingCanvas" width="300" height="150" style="border:2px solid #ccc;"></canvas>
    <script>
        const canvas = document.getElementById('myDrawingCanvas');
        const ctx = canvas.getContext('2d'); // 2D 렌더링 컨텍스트 가져오기
        // 1. 사각형 그리기
        ctx.fillStyle = 'green'; // 채우기 색상 설정
        ctx.fillRect(10, 10, 100, 50); // (x, y, width, height)
        // 2. 원 그리기
        ctx.beginPath(); // 새로운 경로 시작
        ctx.arc(180, 75, 40, 0, 2 * Math.PI); // (x, y, 반지름, 시작각도, 끝각도)
        ctx.fillStyle = 'orange';
        ctx.fill(); // 채우기
        ctx.lineWidth = 3; // 선 두께
        ctx.strokeStyle = 'red'; // 선 색상
        ctx.stroke(); // 선 그리기
        // 3. 텍스트 그리기
        ctx.font = '24px Arial';
        ctx.fillStyle = 'darkblue';
        ctx.textAlign = 'center';
        ctx.fillText('Hello Canvas!', canvas.width / 2, 130); // (텍스트, x, y)
    </script>

SVG와 Canvas의 차이점 및 선택 기준

SVG와 캔버스 기초에서는 태그 의미, 브라우저 해석, 사용자에게 보이는 결과를 연결합니다.

특징SVG (Scalable Vector Graphics)Canvas
기반벡터 기반 (수학적 도형, XML 코드)비트맵/픽셀 기반 (래스터 이미지)
조작 방식각 도형이 DOM 요소로 존재, CSS/JS로 개별 조작 용이전체 캔버스를 하나의 비트맵으로 JS로 픽셀 조작
확장성무한히 확대해도 깨지지 않음 (해상도 독립적)확대하면 픽셀이 보이며 깨짐 (해상도 의존적)
성능요소 수와 갱신 방식에 따라 측정 필요그리는 양·해상도·갱신 방식에 따라 측정 필요
주요 용도로고, 아이콘, 다이어그램, 일러스트, 인터랙티브 차트게임, 이미지 편집, 복잡한 데이터 시각화
접근성요소와 이름·설명을 연결할 수 있으나 별도 설계 필요대체 내용·조작 방법을 별도로 제공해야 함
파일 크기단순한 그래픽은 작고, 복잡한 그래픽은 커질 수 있음해상도와 내용에 따라 다름
언제 무엇을 사용할까?
  • SVG
    • 로고, 아이콘, 지도를 포함한 확장성이 필요한 그래픽.
    • 데이터 변화에 따라 요소별로 색상이나 크기를 바꿔야 하는 인터랙티브 차트/다이어그램.
    • 간단한 애니메이션이나 상태 변화를 CSS/JS로 제어할 필요가 있는 경우.
    • 검색 엔진이나 스크린 리더가 그래픽 내용을 이해해야 하는 경우.
  • Canvas
    • 픽셀 단위의 정교한 그래픽 제어가 필요한 경우 (이미지 필터, 사진 편집기).
    • 높은 프레임 속도를 요구하는 게임이나 복잡한 애니메이션.
    • 수백, 수천 개의 요소가 동적으로 생성/소멸되어야 하는 대규모 데이터 시각화.
    • 그려진 결과물만 중요하고, 각 객체에 대한 개별적인 DOM 조작이 불필요한 경우.

이 절의 통합 다이어그램은 SVG와 Canvas를 선택할 때 먼저 확인할 기준을 한 화면에 정리합니다.


실습: 간단한 Canvas 애니메이션 만들기

두 기술의 기본적인 차이를 직접 경험해 봅시다. JavaScript의 변수·함수·조건이 낯설면 기초 문법과 조건문·반복문을 먼저 참고합니다. 지금은 원의 위치 x, 한 번 갱신할 이동량 dx, 크기 radius를 구분하고 한 번의 갱신부터 따라갑니다.

새 파일 준비
  • web-dev-practice 폴더 안에 graphics_playground.html 파일을 새로 생성합니다.
HTML 구조와 SVG, Canvas 코드 작성
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SVG와 Canvas 기초 실습</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 20px;
            background-color: #f8f8f8;
            color: #333;
            text-align: center;
        }
        .container {
            max-width: 900px;
            margin: 20px auto;
            background-color: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.1);
            display: flex;
            flex-wrap: wrap;
            justify-content: space-around;
            gap: 30px;
        }
        section {
            flex: 1 1 45%; /* 유연하게 공간 차지, 최소 너비 45% */
            min-width: 350px;
            padding: 20px;
            border: 1px solid #eee;
            border-radius: 8px;
            background-color: #fafafa;
        }
        h1 {
            color: #007bff;
            margin-bottom: 40px;
        }
        h2 {
            color: #0056b3;
            margin-top: 0;
        }
        svg {
            display: block;
            margin: 20px auto;
            border: 1px solid #ccc;
            background-color: #fff;
            border-radius: 5px;
        }
        .svg-icon-example {
            display: inline-block; /* 아이콘들이 한 줄에 있도록 */
            margin: 10px;
            padding: 10px;
            border: 1px solid #eee;
            border-radius: 5px;
            transition: transform 0.2s ease-out;
        }
        .svg-icon-example:hover {
            transform: scale(1.1);
            box-shadow: 0 0 8px rgba(0,0,0,0.1);
        }
        .svg-icon-example svg {
            border: none; /* 개별 아이콘은 보더 제거 */
            margin: 0;
        }
        canvas {
            border: 1px solid #ccc;
            background-color: #fff;
            border-radius: 5px;
            display: block;
            margin: 20px auto;
        }
        button {
            padding: 10px 20px;
            background-color: #28a745;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 1em;
            margin-top: 15px;
            transition: background-color 0.3s ease;
        }
        button:hover {
            background-color: #218838;
        }
    </style>
</head>
<body>
    <h1>SVG와 Canvas 기초 실습</h1>

    <div class="container">
        <section>
            <h2>SVG 아이콘 예시 (벡터 그래픽)</h2>
            <p>SVG는 확대해도 깨지지 않는 선명한 벡터 이미지를 제공합니다.</p>

            <div class="svg-icon-example" title="홈 아이콘">
                <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
                    <polyline points="9 22 9 12 15 12 15 22"></polyline>
                </svg>
            </div>

            <div class="svg-icon-example" title="사용자 아이콘">
                <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
                    <circle cx="12" cy="7" r="4"></circle>
                </svg>
            </div>

            <div class="svg-icon-example" title="메일 아이콘">
                <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                    <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
                    <polyline points="22,6 12,13 2,6"></polyline>
                </svg>
            </div>

            <h3>간단한 SVG 도형 그리기</h3>
            <svg width="200" height="150">
                <rect x="20" y="20" width="60" height="60" fill="#ff7f50" stroke="#cd5b45" stroke-width="3">
                    <title>주황색 사각형</title>
                </rect>
                <circle cx="150" cy="75" r="50" fill="#6a5acd" stroke="#483d8b" stroke-width="3">
                    <title>보라색 원</title>
                </circle>
                <text x="100" y="140" text-anchor="middle" font-size="18" fill="#333">SVG 도형들</text>
            </svg>
        </section>

        <section>
            <h2>Canvas 애니메이션 예시 (비트맵 그래픽)</h2>
            <p>Canvas는 자바스크립트로 픽셀 단위의 동적인 그림을 그립니다.</p>

            <canvas id="animatedCanvas" width="350" height="200"></canvas>
            <button id="startButton">애니메이션 시작/정지</button>

            <script>
                const canvas = document.getElementById('animatedCanvas');
                const ctx = canvas.getContext('2d');
                const startButton = document.getElementById('startButton');

                let animationFrameId; // requestAnimationFrame ID
                let x = 50;
                let dx = 2; // x 방향 속도
                let radius = 20;

                function drawCircle() {
                    ctx.clearRect(0, 0, canvas.width, canvas.height); // 캔버스 지우기
                    ctx.beginPath();
                    ctx.arc(x, canvas.height / 2, radius, 0, Math.PI * 2);
                    ctx.fillStyle = 'blue';
                    ctx.fill();
                    ctx.closePath();
                }

                function animate() {
                    drawCircle();
                    x += dx;

                    // 벽에 부딪히면 방향 반전
                    if (x + radius > canvas.width || x - radius < 0) {
                        dx = -dx;
                    }
                    animationFrameId = requestAnimationFrame(animate); // 다음 프레임 요청
                }

                let isAnimating = false;
                startButton.addEventListener('click', () => {
                    if (isAnimating) {
                        cancelAnimationFrame(animationFrameId); // 애니메이션 정지
                        startButton.textContent = '애니메이션 시작';
                    } else {
                        animate(); // 애니메이션 시작
                        startButton.textContent = '애니메이션 정지';
                    }
                    isAnimating = !isAnimating;
                });

                // 페이지 로드 시 초기 상태 그리기
                drawCircle();
            </script>
        </section>
    </div>
</body>
</html>
결과 확인
  • Live Server를 통해 graphics_playground.html 파일을 열어 보세요.
  • SVG 섹션에서는 다양한 아이콘과 도형이 선명하게 보일 것입니다. 브라우저에서 확대/축소해도 깨지지 않는지 확인해 보세요.
  • 처음에는 원 중심이 (50, 100)이고 반지름이 20인 파란 원이 멈춰 있습니다. 버튼의 처음 글자는 애니메이션 시작/정지입니다. 한 번 누르면 원이 움직이며 글자가 애니메이션 정지로, 다시 누르면 멈추며 애니메이션 시작으로 바뀝니다. 세 번째 클릭은 처음 위치로 초기화하지 않고 남아 있는 x에서 다시 움직입니다.

좁은 화면에서도 같은 실습을 확인하기

위 완성 코드는 넓은 화면부터 만든 예입니다. section의 최소 콘텐츠 폭 350px에 좌우 여백 40px와 테두리 2px가 더해져 한 영역만 392px가 필요합니다. 320px나 375px 화면에서는 본문 여백까지 있어 가로로 넘칩니다. 다음 CSS를 같은 파일의 기존 </style> 바로 앞에 추가합니다. 작은 화면에서는 두 영역을 세로로 배치하고 최소 폭을 풀며, Canvas의 표시 크기만 공간에 맞춥니다.

@media (max-width: 600px) {
    body {
        margin: 16px;
    }
    .container {
        padding: 12px;
        gap: 20px;
    }
    .container > section {
        box-sizing: border-box;
        flex-basis: 100%;
        min-width: 0;
        max-width: 100%;
        padding: 12px;
    }
    #animatedCanvas {
        box-sizing: border-box;
        max-width: 100%;
        height: auto;
    }
}

canvas 태그의 width="350" height="200"와 JavaScript는 그대로 둡니다. 내부 좌표계는 여전히 350×200이며, CSS가 비트맵을 같은 가로세로 비율로 작게 표시합니다. 따라서 원 중심 (50, 100)·반지름 20과 벽 반전 조건은 바뀌지 않습니다. 200×150인 SVG 도형 영역과 40×40 아이콘도 이 화면의 콘텐츠 폭 안에 들어가므로 원래 크기로 남깁니다. Canvas가 작아졌다는 이유로 canvas.width를 임의로 바꾸면 그림이 초기화되고 기존 좌표 조건까지 달라집니다.

직접 확인하기

개발자 도구의 반응형 화면을 320px와 375px로 각각 바꾸어 두 영역이 세로로 보이고 페이지에 가로 스크롤이 없는지 확인합니다. 시작·정지·다시 시작을 눌러 버튼 글자와 남은 위치에서의 재시작도 대조합니다. 마지막으로 700px로 넓혔을 때는 위 조건이 꺼지고 기존 배치 규칙으로 돌아가는지 확인합니다.

먼저 예측해 보세요. 화면의 표시 폭이 줄었다고 원의 한 번 갱신 이동량 dx=2를 곧바로 1로 바꿔야 할까요?

좌표와 표시 크기를 구별하는 해설

내부 좌표계의 한 번 갱신은 여전히 2단위입니다. CSS 표시가 작아지면 같은 2단위가 화면에서 차지하는 길이도 비례해 줄어들므로 dx를 바꿀 필요는 없습니다. 이 예제는 프레임마다 2단위씩 움직이며, 실제 초당 속도까지 일정하게 만드는 방식은 앞서 설명한 경과시간 계산과 별도입니다.


한 프레임의 계산을 따라가기

drawCircle()은 현재 x로 캔버스를 지운 뒤 원을 그립니다. animate()는 먼저 그림을 그리고, x += dx로 다음 위치를 준비하고, 벽 조건에 맞으면 dx의 부호를 바꾼 뒤 다음 프레임을 예약합니다. dx = 2는 초당 2px가 아니라 호출 한 번마다 2px입니다. 화면 갱신 빈도가 다르면 초당 이동도 달라질 수 있습니다.

경계에서 스스로 계산하기

폭 350, 반지름 20, x = 330, dx = 2에서 animate()를 한 번 실행한다고 가정합니다. 이번에 그린 중심, 준비된 다음 x, 바뀐 dx를 순서대로 적으세요. 다음 그림이 경계를 약간 넘을 수 있는 이유도 코드 순서로 설명합니다.

계산과 모델의 한계 확인

이번 그림의 중심은 330이고 다음 위치는 332입니다. 332 + 20 > 350이므로 dx는 -2가 됩니다. 다음 호출은 먼저 중심 332에 원을 그리므로 오른쪽 끝이 352, 즉 경계보다 2px 밖으로 나갈 수 있습니다. 방향 반전과 경계 안 위치 보정은 같은 일이 아닙니다. 원을 항상 안에 두는 개선에서는 다음 위치를 반지름 이상·폭에서 반지름을 뺀 값 이하로 보정한 뒤 그리는 순서를 검토합니다.

폭이나 반지름을 바꿀 때도 “원의 중심”이 아닌 x + radius·x - radius를 경계와 비교하는 이유를 먼저 설명해 보세요. SVG의 원은 개별 DOM 요소로 남지만 Canvas의 원은 circle 요소로 생성되는 것이 아닙니다. Canvas 원을 다시 움직이려면 저장한 위치로 픽셀을 다시 그려야 합니다.