방송대 HTML5웹프로그래밍 12강: Canvas 좌표와 그리기 상태
캔버스에 선을 그었는데 흐릿하고, 두 번째 도형의 색과 투명도가 뜻밖에 바뀌며, 새 선이 이전 선과 연결되는 이유는 무엇일까요? Canvas는 도형을 기억해 편집하는 도구가 아니라 현재 좌표·경로·붓 상태로 픽셀을 찍는 시스템입니다. 이 글은 도화지 준비부터 경로 렌더링과 프레임 갱신까지 한 번의 그리기 과정을 실행 순서대로 추적합니다.
탐사선 아이콘 한 프레임에는 여섯 단계가 있다
학습을 위해 탐사선의 상태를 보여 주는 아이콘을 직접 구성한다고 가정해 봅시다. 사각형 프레임 안에 삼각형 탐사선을 그리고, 원형 연료 게이지와 선택 상태를 뜻하는 움직이는 점선을 더합니다. 화면에 보이는 결과는 한 장이지만 브라우저는 다음 여섯 단계를 차례로 수행합니다.
- 도화지 연결:
canvas요소를 찾고 2차원 컨텍스트를 얻습니다. - 픽셀 밀도 동기화: CSS 표시 크기와 내부 픽셀 크기를 맞춥니다.
- 상태 설정: 색, 투명도, 선 두께와 변환을 정합니다.
- 경로 구성: 시작점, 선, 원호를 연결해 아직 보이지 않는 설계도를 만듭니다.
- 렌더링:
fill()이나stroke()로 현재 경로를 픽셀로 남깁니다. - 다음 프레임: 이전 그림을 지우고 상태를 갱신한 뒤 다시 그립니다.
| Canvas가 기억하는 것 | Canvas가 자동으로 관리하지 않는 것 | 학습자가 할 일 |
|---|---|---|
| 현재 그리기 상태 | 도형별 독립 스타일 | save()와 restore()로 범위를 나눔 |
| 현재 경로의 선분 | 다음 도형의 새 경로 시작 | 독립 도형마다 beginPath() 호출 |
| 이미 그려진 픽셀 | 도형 객체의 선택·수정 | 데이터를 보관하고 필요할 때 전체를 다시 그림 |
| 다음 프레임 요청 | 장면 지우기와 상태 갱신 | 지우기 → 갱신 → 그리기 순서를 직접 구성 |
메커니즘의 핵심: Canvas 코드는 ‘도형 목록’보다 ‘명령의 시간 순서’로 읽습니다. 같은 좌표와 색이라도 상태 설정, 경로 시작, 채우기와 선 그리기의 순서가 달라지면 결과가 달라집니다.
canvas 요소는 도화지이고 context는 붓이다
canvas 요소는 그림이 놓일 투명한 사각 영역을 정의합니다. 실제 선과 도형은 JavaScript가 컨텍스트 객체의 메서드를 호출해 그립니다. 캔버스 내부 좌표는 왼쪽 위가 (0, 0), 오른쪽으로 갈수록 x가 커지고 아래로 갈수록 y가 커집니다. 일반적인 수학 좌표와 y축 방향이 반대라는 점을 먼저 표시해야 합니다.
<canvas id="probe-canvas" width="480" height="270"
role="img" aria-label="탐사선 상태 아이콘">
삼각형 탐사선과 60% 연료 게이지를 보여 주는 그림입니다.
</canvas>
width와 height 속성은 내부 드로잉 버퍼의 픽셀 수를 정하며 생략하면 기본 크기 300×150을 사용합니다. 캔버스를 읽기 어려운 환경이나 지원하지 않는 브라우저를 위해 요소 안에는 그림의 의미를 설명하는 대체 텍스트를 둡니다. 의미 있는 그림이면 role="img"와 접근 가능한 이름도 함께 검토합니다.
그리기 작업은 요소 선택, 컨텍스트 생성, 지원 확인, 그리기 명령 순서로 시작합니다.
const canvas = document.querySelector("#probe-canvas");
const ctx = canvas.getContext("2d");
if (!ctx) {
throw new Error("2D 컨텍스트를 사용할 수 없습니다.");
}
ctx.fillStyle = "#ff6b35";
ctx.fillRect(24, 24, 96, 64);
getContext("2d")가 돌려준 ctx가 붓과 도구 상자 역할을 합니다. fillStyle로 현재 채우기 색을 설정한 뒤 fillRect()를 호출했으므로 주황색 사각형이 즉시 픽셀로 그려집니다.
표시 크기와 내부 해상도를 동기화한다
Canvas의 width·height 속성과 CSS로 보이는 크기는 서로 다른 값입니다. 내부 버퍼가 300×150인데 CSS로 600×300까지 늘리면 적은 픽셀을 확대해 보여 주므로 흐릿해질 수 있습니다. 고해상도 디스플레이에서는 CSS 픽셀 하나를 여러 물리 픽셀이 표현하므로 기기의 DPR(Device Pixel Ratio)까지 반영합니다.
CSS 표시 영역이 360×180이고 DPR이 2라면 내부 버퍼는 360×2 = 720, 180×2 = 360으로 만듭니다. 화면에는 계속 360×180으로 보이게 두고, 좌표계만 두 배로 확장하면 이후 코드는 CSS 좌표를 그대로 사용할 수 있습니다.
function resizeCanvas(canvas, ctx) {
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = Math.round(rect.width * dpr);
canvas.height = Math.round(rect.height * dpr);
canvas.style.width = `${rect.width}px`;
canvas.style.height = `${rect.height}px`;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
return { width: rect.width, height: rect.height };
}
getBoundingClientRect()로 CSS 표시 크기를 얻고 내부 버퍼를 DPR배로 만듭니다. setTransform(dpr, 0, 0, dpr, 0, 0)은 이전 변환을 교체하면서 좌표계를 DPR배로 맞춥니다. 리사이즈 때마다 scale(dpr, dpr)만 다시 호출하면 변환이 누적되어 2배가 4배, 8배로 커질 수 있으므로 초기화 또는 setTransform()이 필요합니다.
상태 변화 주의: canvas.width나 canvas.height를 다시 대입하면 픽셀뿐 아니라 색·선·변환 같은 컨텍스트 상태도 초기화됩니다. 크기를 바꾼 뒤에는 해상도 보정과 전체 장면 그리기를 다시 실행해야 합니다.
사각형 명령은 호출 즉시 픽셀을 바꾼다
사각형에는 시작점 (x, y), 폭 w, 높이 h의 네 값이 필요합니다. fillRect(x, y, w, h)는 현재 채우기 색으로 면을, strokeRect()는 현재 선 스타일로 테두리를 즉시 그립니다. clearRect()는 지정 영역의 픽셀을 투명하게 지웁니다.
ctx.fillStyle = "#26364a";
ctx.fillRect(20, 20, 180, 120);
ctx.strokeStyle = "#52d6c7";
ctx.lineWidth = 6;
ctx.strokeRect(230, 20, 180, 120);
ctx.fillStyle = "#ff6b35";
ctx.fillRect(20, 170, 180, 80);
ctx.clearRect(70, 190, 80, 40);
마지막 두 줄은 주황색 면의 일부를 투명하게 파냅니다. clearRect(0, 0, canvas.width, canvas.height)는 흔히 프레임을 지울 때 사용하지만 현재 색과 선 두께 같은 상태는 유지합니다. 반대로 canvas.width = canvas.width는 그림과 상태를 모두 초기화하므로 애니메이션의 매 프레임 지우기에는 보통 clearRect()가 적합합니다.
사각형 명령은 별도의 beginPath()와 fill() 없이 호출 순간 그려집니다. 이 ‘즉시 실행 방식’과 다음의 ‘패스 기반 방식’을 섞어 읽으면 무엇이 아직 설계도이고 무엇이 이미 픽셀인지 놓치기 쉽습니다.
save와 restore는 픽셀이 아니라 상태를 쌓는다
컨텍스트는 한 번 정한 fillStyle, globalAlpha, lineWidth 등을 다음 명령에도 계속 사용합니다. 한 도형의 반투명 설정이 뒤의 글자까지 흐리게 하는 이유입니다. save()는 현재 상태를 스택에 쌓고, restore()는 가장 최근 상태를 꺼내 적용합니다.
ctx.fillStyle = "#172033";
ctx.save(); // 상태 A: 남색
ctx.fillStyle = "#ff6b35";
ctx.globalAlpha = 0.35;
ctx.save(); // 상태 B: 주황, 35%
ctx.fillStyle = "#52d6c7"; // 현재 상태 C
ctx.restore(); // 상태 B 복구
ctx.fillRect(20, 20, 60, 60);
ctx.restore(); // 상태 A 복구
ctx.fillRect(100, 20, 60, 60);
첫 번째 사각형은 상태 B의 주황색과 35% 투명도로, 두 번째는 상태 A의 남색과 그때의 투명도로 그려집니다. restore()는 LIFO(후입선출) 방식이므로 마지막에 저장한 상태부터 나옵니다. 저장한 뒤 그린 픽셀은 상태 스택에 들어가지 않으므로 복구해도 도형이 사라지지 않습니다.
| 저장되는 범주 | 예 | 저장되지 않는 것 |
|---|---|---|
| 색·투명도 | fillStyle, strokeStyle, globalAlpha | 이미 칠한 픽셀 |
| 선 스타일 | lineWidth, lineCap, lineJoin, 점선 상태 | 도형별 편집 정보 |
| 좌표 변환 | translate(), rotate(), scale() | 다음 프레임의 장면 데이터 |
| 텍스트·클리핑·합성 | font, textAlign, clip(), 합성 상태 | 함수의 지역 변수 |
독립된 그리기 함수는 시작에서 save(), 끝에서 restore()를 짝지으면 상태 누출을 줄일 수 있습니다. 다만 저장과 복구 횟수가 맞지 않으면 기대한 상태가 남지 않으므로 중첩 깊이도 함께 추적합니다.
패스는 설계하고 나서 한 번에 렌더링한다
패스 기반 그리기는 먼저 경로를 만들고 마지막에 면이나 선을 렌더링합니다. beginPath()가 현재 경로 목록을 비우고, moveTo()가 선을 긋지 않은 채 현재점을 옮기며, lineTo()가 현재점부터 새 점까지 선분을 추가합니다. closePath()는 현재점과 첫 하위 경로의 시작점을 연결합니다.
ctx.beginPath();
ctx.moveTo(160, 40);
ctx.lineTo(240, 180);
ctx.lineTo(80, 180);
ctx.closePath();
ctx.fillStyle = "#ffcf4a";
ctx.strokeStyle = "#1c7ed6";
ctx.lineWidth = 10;
ctx.fill();
ctx.stroke();
경로를 정의하는 동안에는 화면에 아무것도 보이지 않습니다. fill()과 stroke()가 호출될 때 현재 경로가 픽셀로 바뀝니다. 두꺼운 선은 경로의 안팎으로 절반씩 퍼지므로 선을 먼저 그리고 면을 나중에 채우면 안쪽 절반이 가려질 수 있습니다. 테두리 전체를 선명하게 보이려면 보통 채우기 뒤에 선을 그립니다.
독립된 두 선 사이에 beginPath()가 없으면 두 번째 stroke()가 이전 경로까지 현재 선 색으로 다시 렌더링할 수 있습니다. 이것은 펜이 단지 이어진 문제라기보다 컨텍스트가 이전 경로를 계속 기억하기 때문에 생깁니다.
오개념 교정: closePath()는 현재 경로의 끝을 시작점과 선으로 잇고, beginPath()는 이전 경로 목록을 비웁니다. 하나는 도형을 닫고 다른 하나는 경로 기억을 새로 시작하므로 서로 대신할 수 없습니다.
좌표 배열은 다각형 경로로 변환된다
다각형은 좌표 배열을 입력으로 받아 패스로 변환할 수 있습니다. 이때 첫 점은 moveTo(), 나머지 점은 lineTo(), 마지막은 closePath()로 연결합니다. 다음은 강의 내용을 바탕으로 새 좌표와 색을 사용한 학습용 함수입니다.
function drawPolygon(ctx, points, style) {
if (points.length < 3) return;
ctx.save();
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
points.slice(1).forEach(point => {
ctx.lineTo(point.x, point.y);
});
ctx.closePath();
ctx.fillStyle = style.fillStyle ?? "transparent";
ctx.strokeStyle = style.strokeStyle ?? "#172033";
ctx.lineWidth = style.lineWidth ?? 1;
ctx.fill();
ctx.stroke();
ctx.restore();
}
drawPolygon(ctx, [
{ x: 150, y: 45 },
{ x: 225, y: 185 },
{ x: 150, y: 150 },
{ x: 75, y: 185 }
], {
fillStyle: "#7bdff2",
strokeStyle: "#172033",
lineWidth: 6
});
점이 3개보다 적으면 면을 가진 다각형이 성립하지 않으므로 조기 반환합니다. slice(1)은 첫 좌표를 제외한 새 배열을 만들고, forEach()가 나머지 좌표를 차례로 경로에 추가합니다. 스타일 기본값에는 || 대신 ??를 사용해 값 없음(null·undefined)과 다른 거짓 같은 값을 구분했습니다. 단, lineWidth 자체는 양수여야 하므로 실제 함수에서는 범위 검사도 함께 두는 편이 안전합니다.
Path2D는 경로 설계도를 객체로 보관해 다시 그릴 수 있게 합니다. new Path2D()에 moveTo(), lineTo(), arc() 등을 호출한 뒤 ctx.fill(path) 또는 ctx.stroke(path)에 전달합니다. new Path2D(existingPath)로 기존 경로를 복사하고 새 경로를 더할 수도 있어, 경로 정의와 렌더링 스타일을 분리하기 좋습니다.
arc는 라디안과 현재점을 함께 사용한다
arc(x, y, radius, startAngle, endAngle, counterclockwise)는 원과 원호의 경로를 만듭니다. (x, y)는 중심, radius는 반지름이며 각도 단위는 도가 아니라 라디안입니다. Canvas의 0라디안은 원의 오른쪽인 3시 방향이고, 마지막 인수가 생략되거나 false이면 시계 방향으로 진행합니다.
const degToRad = degree => degree * (Math.PI / 180);
ctx.beginPath();
ctx.arc(150, 150, 80, 0, degToRad(270));
ctx.strokeStyle = "#ff6b35";
ctx.lineWidth = 8;
ctx.stroke();
270도는 270 × π/180 = 3π/2라디안입니다. 시작점 0은 3시 방향이고 시계 방향으로 270도 진행하므로 오른쪽 → 아래 → 왼쪽 → 위쪽까지 이어지는 3/4 원호가 됩니다.
부채꼴은 원호의 양 끝을 중심과 연결해야 합니다. 먼저 중심으로 moveTo()하고 원호를 만든 뒤 closePath()로 마지막 점을 시작점인 중심에 연결합니다.
ctx.beginPath();
ctx.moveTo(150, 150);
ctx.arc(150, 150, 80, degToRad(30), degToRad(120));
ctx.closePath();
ctx.fillStyle = "#ffcf4a";
ctx.fill();
ctx.stroke();
중심 이동을 생략하면 현재점에서 원호 시작점까지 예상하지 않은 선이 생기거나 중심이 없는 활 모양으로 닫힐 수 있습니다. 원호 자체와 부채꼴의 차이는 각도 값이 아니라 중심점이 경로에 포함되는지에 있습니다.
파이 조각은 이전 끝각을 다음 시작각으로 넘긴다
파이 차트는 각 데이터의 비율을 한 바퀴인 2π에 곱하고, 조각을 그릴 때마다 현재 각도를 누적합니다. 탐사선 에너지 사용량을 추진 5, 통신 3, 센서 2로 가정하면 합계는 10입니다.
| 항목 | 비율 | 조각 각도 | 누적 구간 |
|---|---|---|---|
| 추진 | 5/10 = 0.5 | 0.5 × 360° = 180° | 0°~180° |
| 통신 | 3/10 = 0.3 | 0.3 × 360° = 108° | 180°~288° |
| 센서 | 2/10 = 0.2 | 0.2 × 360° = 72° | 288°~360° |
세 각도의 합은 180 + 108 + 72 = 360°이므로 한 바퀴와 일치합니다. 코드에서는 도 대신 라디안으로 계산합니다.
function drawPie(ctx, data, centerX, centerY, radius) {
const total = data.reduce((sum, item) => sum + item.value, 0);
let currentAngle = 0;
data.forEach(item => {
const sliceAngle = (item.value / total) * Math.PI * 2;
ctx.save();
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.arc(
centerX,
centerY,
radius,
currentAngle,
currentAngle + sliceAngle
);
ctx.closePath();
ctx.fillStyle = item.color;
ctx.fill();
ctx.strokeStyle = "white";
ctx.stroke();
ctx.restore();
currentAngle += sliceAngle;
});
}
각 조각의 시작각은 currentAngle, 끝각은 currentAngle + sliceAngle입니다. 렌더링을 마친 뒤 현재 각도에 이번 조각을 더해야 다음 조각이 빈틈 없이 이어집니다. 더하기를 반복문 앞에서 하면 첫 조각의 시작부터 밀리고, 갱신하지 않으면 모든 조각이 같은 시작점에 겹칩니다.
선의 두께·끝·이음은 경로 주변의 모양을 바꾼다
fillStyle은 면, strokeStyle은 선의 색을 정하며 CSS 색상 표현을 사용할 수 있습니다. lineWidth는 경로 중심을 기준으로 안팎으로 퍼집니다. 폭 20인 사각형 선은 경로 안쪽과 바깥쪽으로 각각 약 10씩 차지하므로 캔버스 가장자리에 붙이면 일부가 잘릴 수 있습니다.
lineCap은 열린 선 끝, lineJoin은 두 선분이 만나는 모서리를 다룹니다.
| 속성 | 값 | 결과를 확인할 위치 |
|---|---|---|
lineCap | butt, round, square | 열린 경로의 양 끝 |
lineJoin | miter, round, bevel | 두 선분이 만나는 꼭짓점 |
lineWidth | 양수 | 경로 중심의 안팎 |
투명도는 범위로 구분합니다. rgba()의 알파는 그 색을 사용한 특정 도형에만 포함됩니다. globalAlpha는 이후의 모든 그리기 명령에 곱해지는 전역 상태이므로 원복하지 않으면 뒤의 도형도 투명해집니다. 도형 하나에만 적용하려면 save()와 restore()로 감싸거나 개별 색상의 알파를 사용합니다.
점선 패턴은 배열을 순환하고 offset은 시작점을 민다
setLineDash()는 실선 길이와 공백 길이를 차례로 담은 배열을 반복합니다. [12, 6]은 12px 선과 6px 공백을 주기 18px로 반복합니다. 요소 수가 홀수이면 배열을 한 번 이어 붙여 짝수 길이 패턴으로 만듭니다. 따라서 [8, 4, 2]는 [8, 4, 2, 8, 4, 2]처럼 해석됩니다.
lineDashOffset은 패턴의 출발 위치를 이동합니다. 값을 프레임마다 바꾸면 선 자체의 좌표는 그대로인데 점선 무늬만 흐르는 것처럼 보입니다. 점선 상태도 컨텍스트에 남으므로 다음 실선을 그리기 전 setLineDash([])로 초기화하거나 상태 블록을 복구합니다.
let dashOffset = 0;
function drawSelection(ctx) {
ctx.save();
ctx.setLineDash([12, 6]);
ctx.lineDashOffset = -dashOffset;
ctx.lineWidth = 2;
ctx.strokeStyle = "#1c7ed6";
ctx.strokeRect(12, 12, 276, 176);
ctx.restore();
}
주기가 18px이므로 dashOffset을 0부터 17까지 변화시킨 뒤 0으로 되돌리면 같은 패턴이 자연스럽게 반복됩니다. 양수와 음수는 무늬가 이동해 보이는 방향을 바꾸며, 좌표 경로 자체를 이동시키는 변환과는 다릅니다.
애니메이션은 지우기·갱신·그리기·예약을 반복한다
requestAnimationFrame()은 브라우저가 다음 화면을 그릴 기회에 콜백을 실행하도록 예약합니다. 자동 반복 기능이 아니므로 콜백 끝에서 다음 프레임을 다시 요청해야 합니다. 기본 구조는 이전 프레임 지우기, 상태값 갱신, 장면 다시 그리기, 다음 호출 예약입니다.
let dashOffset = 0;
let requestId;
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
dashOffset = (dashOffset + 1) % 18;
drawProbeBody(ctx);
drawFuelGauge(ctx);
drawSelection(ctx);
requestId = requestAnimationFrame(animate);
}
function stopAnimation() {
cancelAnimationFrame(requestId);
}
animate();
지우기를 빼면 이전 프레임의 점선이 남아 잔상처럼 쌓이고, 갱신을 빼면 같은 장면만 반복합니다. 그리기 전에 상태를 갱신했으므로 첫 프레임부터 offset 1이 사용됩니다. 0 상태를 먼저 보여 주고 싶다면 갱신을 그리기 뒤로 옮길 수 있습니다. 이처럼 네 단계의 순서는 첫 장면과 잔상 여부까지 바꿉니다.
requestAnimationFrame()의 반환값을 저장하면 cancelAnimationFrame()으로 예약된 다음 실행을 취소할 수 있습니다. 중지 함수가 그리기 코드를 지우는 것은 아니므로 마지막 프레임의 픽셀은 남습니다. 멈춘 뒤 화면까지 비우려면 별도 clearRect()가 필요합니다.
한 프레임을 재현하는 실행 점검표
새 Canvas 코드를 읽거나 예상과 다른 결과를 고칠 때는 다음 순서로 한 프레임을 재현합니다.
- 크기: CSS 표시 크기, 내부
width·height, DPR 보정값을 각각 적습니다. - 좌표: (0, 0)의 위치와 x·y 증가 방향, 현재 변환의 누적 여부를 확인합니다.
- 상태: 색·투명도·선·점선 상태를 누가 설정했고 어느
restore()에서 돌아오는지 스택으로 표시합니다. - 경로: 즉시 실행 명령인지,
beginPath()이후의 설계도인지 나눕니다. - 렌더링:
fill()과stroke()의 호출 순서, 선 두께가 덮이는 범위를 확인합니다. - 프레임: 지우기·상태 갱신·그리기·다음 예약이 모두 있고 중지할 요청 ID를 보관하는지 확인합니다.
탐사선 아이콘이 흐리면 1단계, 다른 도형의 색이 섞이면 3단계, 선이 이전 도형과 이어지면 4단계, 테두리 절반이 가리면 5단계, 잔상이 남으면 6단계부터 조사합니다. 증상과 실행 단계를 연결하면 속성값을 무작정 바꾸지 않고 원인을 좁힐 수 있습니다.
핵심 개념 정리
- Canvas는 왼쪽 위 원점에서 픽셀을 그리며, 요소의 내부 해상도와 CSS 표시 크기는 별개입니다.
- DPR 보정은 표시 크기를 측정하고 내부 버퍼를 확대한 뒤 좌표계를 같은 비율로 맞추는 과정입니다.
- 사각형 명령은 즉시 픽셀을 바꾸지만 패스 명령은 경로를 구성한 뒤
fill()·stroke()에서 보입니다. save()·restore()는 색·선·변환 상태를 후입선출로 관리하며 이미 그린 픽셀을 되돌리지 않습니다.- 원호는 3시 방향의 0라디안에서 시작하고, 부채꼴은 중심점·원호·닫기를 하나의 경로로 연결합니다.
- 점선 애니메이션은 경로 이동이 아니라
lineDashOffset갱신과 프레임 재렌더링으로 만듭니다.
최종 실행 순서: Canvas 결과를 예측하려면 도화지 크기와 좌표계를 확정하고, 상태를 저장한 뒤, 새 경로를 구성해 채우기와 선을 렌더링하고, 상태를 복구합니다. 움직임이 필요하면 이 한 프레임을 지우기·갱신·그리기·예약 순환에 넣습니다. 다음 차시의 그라데이션·이미지·텍스트·변형도 이 상태와 명령 순서 위에 추가됩니다.
예상문제 10선
1. Canvas 요소의 width·height HTML 속성이 정하는 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
- ① 오답: 여백과 테두리는 CSS 박스 표현이며 내부 픽셀 해상도와 다른 층입니다.
- ② 정답: 두 속성은 Canvas가 실제 그림을 담을 가로·세로 픽셀 수를 정합니다.
- ③ 오답: 선 두께는
lineWidth, 좌표 변환은 컨텍스트 변환 메서드가 담당합니다. - ④ 오답: 요소 크기는 저장 가능한 경로 객체의 개수를 제한하는 속성이 아닙니다.
2. CSS 표시 크기가 320×180이고 DPR이 2.5일 때 선명도 보정을 위한 내부 버퍼 크기는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
- ① 오답: 표시 크기를 DPR로 나눈 값이라 내부 픽셀을 오히려 줄였습니다.
- ② 오답: CSS 크기만 사용해 2.5배 물리 픽셀 밀도를 반영하지 않았습니다.
- ③ 오답: DPR을 2로 가정한 결과이며 문제의 2.5와 다릅니다.
- ④ 정답: 320×2.5 = 800, 180×2.5 = 450으로 계산합니다.
3. 매 애니메이션 프레임에서 그림만 지우고 기존 선·색·변환 상태는 유지하려는 방법은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
- ① 오답: 크기 재대입은 픽셀뿐 아니라 컨텍스트 상태까지 초기화합니다.
- ② 오답: 현재 경로를 새로 시작하지만 이미 그려진 픽셀은 지우지 않습니다.
- ③ 정답: 지정한 픽셀 영역을 투명하게 지우면서 현재 그리기 상태는 유지합니다.
- ④ 오답: 저장 상태를 복구하지만 화면 픽셀을 삭제하는 메서드는 아닙니다.
4. 남색 상태 저장 → 주황색으로 변경 후 저장 → 청록색으로 변경 → restore() 순서에서 복구되는 색은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
- ① 정답: 상태 스택은 후입선출이므로 가장 나중에 저장한 주황색 상태가 먼저 나옵니다.
- ② 오답: 남색은 먼저 저장했으므로 restore를 한 번 더 호출해야 복구됩니다.
- ③ 오답: 청록색은 저장하지 않은 현재 상태라 restore 순간 버려집니다.
- ④ 오답: 저장된 상태가 남아 있으므로 초기 기본값까지 돌아가지 않습니다.
5. 서로 독립된 빨간 선과 파란 선을 그렸는데 두 번째 stroke()에서 첫 선도 파란색으로 다시 보였다. 가장 직접적인 원인은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
- ① 오답:
closePath()는 현재 경로의 끝과 시작을 잇지만 이전 경로 기억을 비우지 않습니다. - ② 오답: 픽셀을 지웠다면 첫 선이 사라질 뿐 새 선 색으로 다시 렌더링되는 원인을 설명하지 못합니다.
- ③ 오답:
save()는 상태를 저장하며 현재 경로 목록을 복제하거나 비우지 않습니다. - ④ 정답: 기존 경로가 남은 채 현재 파란색으로 전체 경로를 다시 stroke해 첫 선도 덧그려졌습니다.
6. 120도를 Canvas arc()에 사용할 라디안으로 바꾼 값은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
- ① 오답: π/3은 60도에 해당해 요구 각도의 절반입니다.
- ② 정답: 120 × π/180에서 60으로 약분하면 2π/3입니다.
- ③ 오답: 3π/2는 270도로 120도보다 큽니다.
- ④ 오답: 2π는 한 바퀴인 360도입니다.
7. 노란 면과 두께 12px의 파란 테두리가 있는 다각형에서 테두리 전체가 면 위에 선명하게 보이게 하는 순서는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
- ① 정답: 면을 먼저 칠하고 선을 나중에 덧그려 경로 안쪽 절반도 가리지 않고 보입니다.
- ② 오답: 나중의 채우기가 경로 안쪽으로 퍼진 선의 일부를 덮을 수 있습니다.
- ③ 오답: 경로가 없는 상태의 fill은 원하는 다각형을 그리지 못하고 beginPath가 앞 경로를 비웁니다.
- ④ 오답: stroke 전에 경로를 정의해야 하며 지우기는 렌더링 순서를 대신하지 않습니다.
8. 열린 꺾은선의 양 끝은 둥글고, 선분이 만나는 꼭짓점도 둥글게 하려는 설정은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
- ① 오답: 이음은 둥글지만
butt인 열린 선 끝은 경로 끝에서 평평하게 잘립니다. - ② 오답: 끝은 둥글지만
miter이음은 바깥 모서리가 뾰족하게 만납니다. - ③ 정답: cap이 열린 끝을, join이 꼭짓점을 각각 둥글게 만듭니다.
- ④ 오답: square 끝과 bevel 이음은 모두 요구한 둥근 모양과 다릅니다.
9. ctx.setLineDash([8, 4, 2])의 실제 반복 배열로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
- ① 오답: 마지막 값 2를 버리지 않으며 홀수 길이를 처리하는 규칙과 맞지 않습니다.
- ② 정답: 홀수 개 요소 배열은 같은 배열을 한 번 이어 붙여 짝수 개 패턴으로 반복합니다.
- ③ 오답: 브라우저가 임의의 0 공백을 덧붙이는 방식이 아닙니다.
- ④ 오답: 각 길이를 두 배로 확대하지 않고 배열 전체를 복제합니다.
10. 움직이는 점선 선택 테두리에서 잔상을 남기지 않고 반복하며 나중에 중지할 수 있는 구조는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
- ① 오답: 이미 그린 픽셀은 변수 변경만으로 바뀌지 않아 새 상태를 다시 렌더링해야 합니다.
- ② 오답: 지우기 순서가 뒤라 장면을 표시하지 못하고 직접 재귀는 브라우저 프레임 예약과 취소를 사용하지 않습니다.
- ③ 정답: 이전 픽셀을 제거하고 새 상태를 그린 뒤 ID를 보관해 다음 요청을 취소할 수 있습니다.
- ④ 오답: 크기 재대입은 컨텍스트 상태까지 초기화하므로 스타일 유지 가정이 틀립니다.
참고 자료와 작성 기준
이 글은 해당 차시 강의자료 50쪽 전체를 확인한 뒤 표지·목차·단원 구분·정리·다음 차시 예고를 본문과 구분하고, Canvas 명령을 한 프레임의 실행 과정으로 재구성한 비공식 학습자료입니다. 강의자료의 범위 안에서 탐사선 상태 아이콘, DPR 계산, 상태 스택 추적, 다각형 함수, 파이 조각 누적각, 점선 프레임 점검과 선택지별 해설을 새로 구성했습니다.
- 작성·편집: 올에이클래스 학습연구팀
- 주요 근거: 한국방송통신대학교 HTML5웹프로그래밍 12강 「캔버스 (1)」 강의자료
- 보충 자료: 외부 자료를 사용하지 않았으며, 강의자료의 설명 범위 안에서 예제와 실행 추적을 재구성했습니다.
- 편집 원칙: 올에이클래스 편집 정책
- 최종 내용 검토: 2026-09-05
댓글
댓글 쓰기