여러분이 사용하고 계신 브라우저는 HTML5를 지원하지 않기 때문에 몇몇 요소가 제대로 보이도록 JScript를 사용하고 있습니다. 하지만 여러분의 브라우저 설정에서 스크립트 기능이 꺼져있으므로, 현재 페이지를 제대로 확인하시려면 스크립트 기능을 켜주셔야 합니다. Canvas 시계 만들기

Canvas 시계 만들기

2주전 작성

HTML Canvas 시계 만들기 시작

HTML Canvas를 사용하여 아날로그 시계를 만드는 방법을 단계별로 알아본다. 이 튜토리얼에서는 기본 설정부터 완전한 시계 완성까지의 전체 과정을 설명한다.

1단계: 기본 캔버스 설정

먼저 시계를 그릴 기본 캔버스를 설정한다.

<canvas id="canvas" width="400" height="400" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var radius = canvas.height / 2;
ctx.translate(radius, radius);
radius = radius * 0.90;
drawClock();
function drawClock() {
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.fillStyle = 'white';
ctx.fill();
}
</script>

이 코드는 기본적인 원형 시계 베이스를 만든다. translate() 메서드를 사용하여 좌표계의 중심을 캔버스 중앙으로 이동시킨다.

2단계: 시계 페이스 개선

시계 페이스에 그라데이션 테두리를 추가하여 더 현실적으로 만들어보자.

<canvas id="canvas2" width="400" height="400" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var canvas = document.getElementById("canvas2");
var ctx = canvas.getContext("2d");
var radius = canvas.height / 2;
ctx.translate(radius, radius);
radius = radius * 0.90;
drawClock();
function drawClock() {
drawFace(ctx, radius);
}
function drawFace(ctx, radius) {
var grad;
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.fillStyle = 'white';
ctx.fill();
grad = ctx.createRadialGradient(0, 0, radius * 0.95, 0, 0, radius * 1.05);
grad.addColorStop(0, '#333');
grad.addColorStop(0.5, 'white');
grad.addColorStop(1, '#333');
ctx.strokeStyle = grad;
ctx.lineWidth = radius * 0.1;
ctx.stroke();
ctx.beginPath();
ctx.arc(0, 0, radius * 0.1, 0, 2 * Math.PI);
ctx.fillStyle = '#333';
ctx.fill();
}
</script>

drawFace() 함수는 다음과 같은 요소들을 그린다.

  • 흰색 배경의 원형 페이스
  • 그라데이션 효과가 있는 테두리
  • 중앙의 작은 원 (바늘의 중심점)

3단계: 숫자 추가

시계 페이스에 1부터 12까지의 숫자를 추가한다.

<canvas id="canvas3" width="400" height="400" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var canvas = document.getElementById("canvas3");
var ctx = canvas.getContext("2d");
var radius = canvas.height / 2;
ctx.translate(radius, radius);
radius = radius * 0.90;
drawClock();
function drawClock() {
drawFace(ctx, radius);
drawNumbers(ctx, radius);
}
function drawFace(ctx, radius) {
var grad;
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.fillStyle = 'white';
ctx.fill();
grad = ctx.createRadialGradient(0, 0, radius * 0.95, 0, 0, radius * 1.05);
grad.addColorStop(0, '#333');
grad.addColorStop(0.5, 'white');
grad.addColorStop(1, '#333');
ctx.strokeStyle = grad;
ctx.lineWidth = radius * 0.1;
ctx.stroke();
ctx.beginPath();
ctx.arc(0, 0, radius * 0.1, 0, 2 * Math.PI);
ctx.fillStyle = '#333';
ctx.fill();
}
function drawNumbers(ctx, radius) {
var ang;
var num;
ctx.font = radius * 0.15 + "px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
for (num = 1; num < 13; num++) { ang = num * Math.PI / 6; ctx.rotate(ang); ctx.translate(0, -radius * 0.85); ctx.rotate(-ang); ctx.fillText(num.toString(), 0, 0); ctx.rotate(ang); ctx.translate(0, radius * 0.85); ctx.rotate(-ang); } } </script>[/code]

drawNumbers() 함수는 1부터 12까지의 숫자를 원형으로 배치한다. 각 숫자는 30도(π/6 라디안)씩 회전하여 배치된다.

4단계: 시계 바늘 추가

시침, 분침, 초침을 추가하여 시간을 표시한다.

<canvas id="canvas4" width="400" height="400" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var canvas = document.getElementById("canvas4");
var ctx = canvas.getContext("2d");
var radius = canvas.height / 2;
ctx.translate(radius, radius);
radius = radius * 0.90;
drawClock();
function drawClock() {
drawFace(ctx, radius);
drawNumbers(ctx, radius);
drawTime(ctx, radius);
}
function drawFace(ctx, radius) {
var grad;
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.fillStyle = 'white';
ctx.fill();
grad = ctx.createRadialGradient(0, 0, radius * 0.95, 0, 0, radius * 1.05);
grad.addColorStop(0, '#333');
grad.addColorStop(0.5, 'white');
grad.addColorStop(1, '#333');
ctx.strokeStyle = grad;
ctx.lineWidth = radius * 0.1;
ctx.stroke();
ctx.beginPath();
ctx.arc(0, 0, radius * 0.1, 0, 2 * Math.PI);
ctx.fillStyle = '#333';
ctx.fill();
}
function drawNumbers(ctx, radius) {
var ang;
var num;
ctx.font = radius * 0.15 + "px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
for (num = 1; num < 13; num++) { ang = num * Math.PI / 6; ctx.rotate(ang); ctx.translate(0, -radius * 0.85); ctx.rotate(-ang); ctx.fillText(num.toString(), 0, 0); ctx.rotate(ang); ctx.translate(0, radius * 0.85); ctx.rotate(-ang); } } function drawTime(ctx, radius) { var now = new Date(); var hour = now.getHours(); var minute = now.getMinutes(); var second = now.getSeconds(); hour = hour % 12; hour = (hour * Math.PI / 6) + (minute * Math.PI / (6 * 60)) + (second * Math.PI / (360 * 60)); drawHand(ctx, hour, radius * 0.5, radius * 0.07); minute = (minute * Math.PI / 30) + (second * Math.PI / (30 * 60)); drawHand(ctx, minute, radius * 0.8, radius * 0.07); second = (second * Math.PI / 30); drawHand(ctx, second, radius * 0.9, radius * 0.02); } function drawHand(ctx, pos, length, width) { ctx.beginPath(); ctx.lineWidth = width; ctx.lineCap = "round"; ctx.moveTo(0, 0); ctx.rotate(pos); ctx.lineTo(0, -length); ctx.stroke(); ctx.rotate(-pos); } </script>[/code]

drawTime() 함수는 현재 시간을 가져와서 각 바늘의 각도를 계산하고, drawHand() 함수는 실제 바늘을 그린다.

5단계: 실시간 업데이트

시계가 실시간으로 업데이트되도록 만들어보자.

<canvas id="canvas5" width="400" height="400" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var canvas = document.getElementById("canvas5");
var ctx = canvas.getContext("2d");
var radius = canvas.height / 2;
ctx.translate(radius, radius);
radius = radius * 0.90;
setInterval(drawClock, 1000);
function drawClock() {
ctx.clearRect(-radius, -radius, radius * 2, radius * 2);
drawFace(ctx, radius);
drawNumbers(ctx, radius);
drawTime(ctx, radius);
}
function drawFace(ctx, radius) {
var grad;
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.fillStyle = 'white';
ctx.fill();
grad = ctx.createRadialGradient(0, 0, radius * 0.95, 0, 0, radius * 1.05);
grad.addColorStop(0, '#333');
grad.addColorStop(0.5, 'white');
grad.addColorStop(1, '#333');
ctx.strokeStyle = grad;
ctx.lineWidth = radius * 0.1;
ctx.stroke();
ctx.beginPath();
ctx.arc(0, 0, radius * 0.1, 0, 2 * Math.PI);
ctx.fillStyle = '#333';
ctx.fill();
}
function drawNumbers(ctx, radius) {
var ang;
var num;
ctx.font = radius * 0.15 + "px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
for (num = 1; num < 13; num++) { ang = num * Math.PI / 6; ctx.rotate(ang); ctx.translate(0, -radius * 0.85); ctx.rotate(-ang); ctx.fillText(num.toString(), 0, 0); ctx.rotate(ang); ctx.translate(0, radius * 0.85); ctx.rotate(-ang); } } function drawTime(ctx, radius) { var now = new Date(); var hour = now.getHours(); var minute = now.getMinutes(); var second = now.getSeconds(); hour = hour % 12; hour = (hour * Math.PI / 6) + (minute * Math.PI / (6 * 60)) + (second * Math.PI / (360 * 60)); drawHand(ctx, hour, radius * 0.5, radius * 0.07); minute = (minute * Math.PI / 30) + (second * Math.PI / (30 * 60)); drawHand(ctx, minute, radius * 0.8, radius * 0.07); second = (second * Math.PI / 30); drawHand(ctx, second, radius * 0.9, radius * 0.02); } function drawHand(ctx, pos, length, width) { ctx.beginPath(); ctx.lineWidth = width; ctx.lineCap = "round"; ctx.moveTo(0, 0); ctx.rotate(pos); ctx.lineTo(0, -length); ctx.stroke(); ctx.rotate(-pos); } </script>[/code]

setInterval()을 사용하여 1초마다 시계를 다시 그린다. clearRect()로 이전 그림을 지우고 새로운 시간으로 업데이트한다.

6단계: 색상과 스타일 개선

시계에 색상과 스타일을 추가하여 더 매력적으로 만들어보자.

<canvas id="canvas6" width="400" height="400" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var canvas = document.getElementById("canvas6");
var ctx = canvas.getContext("2d");
var radius = canvas.height / 2;
ctx.translate(radius, radius);
radius = radius * 0.90;
setInterval(drawClock, 1000);
function drawClock() {
ctx.clearRect(-radius, -radius, radius * 2, radius * 2);
drawFace(ctx, radius);
drawNumbers(ctx, radius);
drawTime(ctx, radius);
}
function drawFace(ctx, radius) {
var grad;
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.fillStyle = '#f8f9fa';
ctx.fill();
grad = ctx.createRadialGradient(0, 0, radius * 0.95, 0, 0, radius * 1.05);
grad.addColorStop(0, '#2c3e50');
grad.addColorStop(0.5, '#34495e');
grad.addColorStop(1, '#2c3e50');
ctx.strokeStyle = grad;
ctx.lineWidth = radius * 0.1;
ctx.stroke();
ctx.beginPath();
ctx.arc(0, 0, radius * 0.1, 0, 2 * Math.PI);
ctx.fillStyle = '#e74c3c';
ctx.fill();
}
function drawNumbers(ctx, radius) {
var ang;
var num;
ctx.font = "bold " + radius * 0.15 + "px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "#2c3e50";
for (num = 1; num < 13; num++) { ang = num * Math.PI / 6; ctx.rotate(ang); ctx.translate(0, -radius * 0.85); ctx.rotate(-ang); ctx.fillText(num.toString(), 0, 0); ctx.rotate(ang); ctx.translate(0, radius * 0.85); ctx.rotate(-ang); } } function drawTime(ctx, radius) { var now = new Date(); var hour = now.getHours(); var minute = now.getMinutes(); var second = now.getSeconds(); hour = hour % 12; hour = (hour * Math.PI / 6) + (minute * Math.PI / (6 * 60)) + (second * Math.PI / (360 * 60)); drawHand(ctx, hour, radius * 0.5, radius * 0.07, "#2c3e50"); minute = (minute * Math.PI / 30) + (second * Math.PI / (30 * 60)); drawHand(ctx, minute, radius * 0.8, radius * 0.07, "#34495e"); second = (second * Math.PI / 30); drawHand(ctx, second, radius * 0.9, radius * 0.02, "#e74c3c"); } function drawHand(ctx, pos, length, width, color) { ctx.beginPath(); ctx.lineWidth = width; ctx.lineCap = "round"; ctx.strokeStyle = color; ctx.moveTo(0, 0); ctx.rotate(pos); ctx.lineTo(0, -length); ctx.stroke(); ctx.rotate(-pos); } </script>[/code]

이 개선된 버전에서는 다음과 같은 스타일을 적용했다.

  • 연한 회색 배경
  • 진한 파란색 테두리와 숫자
  • 빨간색 중앙점과 초침
  • 굵은 폰트

7단계: 시간 마크 추가

시계에 시간과 분을 나타내는 마크를 추가해보자.

<canvas id="canvas7" width="400" height="400" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var canvas = document.getElementById("canvas7");
var ctx = canvas.getContext("2d");
var radius = canvas.height / 2;
ctx.translate(radius, radius);
radius = radius * 0.90;
setInterval(drawClock, 1000);
function drawClock() {
ctx.clearRect(-radius, -radius, radius * 2, radius * 2);
drawFace(ctx, radius);
drawNumbers(ctx, radius);
drawMarks(ctx, radius);
drawTime(ctx, radius);
}
function drawFace(ctx, radius) {
var grad;
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.fillStyle = 'white';
ctx.fill();
grad = ctx.createRadialGradient(0, 0, radius * 0.95, 0, 0, radius * 1.05);
grad.addColorStop(0, '#333');
grad.addColorStop(0.5, 'white');
grad.addColorStop(1, '#333');
ctx.strokeStyle = grad;
ctx.lineWidth = radius * 0.1;
ctx.stroke();
ctx.beginPath();
ctx.arc(0, 0, radius * 0.1, 0, 2 * Math.PI);
ctx.fillStyle = '#333';
ctx.fill();
}
function drawNumbers(ctx, radius) {
var ang;
var num;
ctx.font = radius * 0.15 + "px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "black";
for (num = 1; num < 13; num++) { ang = num * Math.PI / 6; ctx.rotate(ang); ctx.translate(0, -radius * 0.85); ctx.rotate(-ang); ctx.fillText(num.toString(), 0, 0); ctx.rotate(ang); ctx.translate(0, radius * 0.85); ctx.rotate(-ang); } } function drawMarks(ctx, radius) { var ang; var mark; for (mark = 0; mark < 60; mark++) { ang = mark * Math.PI / 30; ctx.rotate(ang); ctx.translate(0, -radius * 0.93); ctx.rotate(-ang); if (mark % 5 === 0) { ctx.beginPath(); ctx.arc(0, 0, radius * 0.03, 0, 2 * Math.PI); ctx.fillStyle = '#333'; ctx.fill(); } else { ctx.beginPath(); ctx.arc(0, 0, radius * 0.015, 0, 2 * Math.PI); ctx.fillStyle = '#666'; ctx.fill(); } ctx.rotate(ang); ctx.translate(0, radius * 0.93); ctx.rotate(-ang); } } function drawTime(ctx, radius) { var now = new Date(); var hour = now.getHours(); var minute = now.getMinutes(); var second = now.getSeconds(); hour = hour % 12; hour = (hour * Math.PI / 6) + (minute * Math.PI / (6 * 60)) + (second * Math.PI / (360 * 60)); drawHand(ctx, hour, radius * 0.5, radius * 0.07, "#333"); minute = (minute * Math.PI / 30) + (second * Math.PI / (30 * 60)); drawHand(ctx, minute, radius * 0.8, radius * 0.07, "#333"); second = (second * Math.PI / 30); drawHand(ctx, second, radius * 0.9, radius * 0.02, "red"); } function drawHand(ctx, pos, length, width, color) { ctx.beginPath(); ctx.lineWidth = width; ctx.lineCap = "round"; ctx.strokeStyle = color; ctx.moveTo(0, 0); ctx.rotate(pos); ctx.lineTo(0, -length); ctx.stroke(); ctx.rotate(-pos); } </script>[/code]

drawMarks() 함수는 60개의 분 마크를 그린다. 5분 간격(시간 마크)은 큰 점으로, 나머지는 작은 점으로 표시한다.

💡 시계 제작 팁:
• 모든 크기를 반지름에 비례하여 설정하면 다양한 크기의 시계를 만들 수 있다
• translate()를 사용하여 좌표계를 시계 중심으로 이동시키면 계산이 쉬워진다
• 각도 계산 시 분과 초의 영향을 고려하여 연속적인 움직임을 만들어야 한다
• 성능을 위해 적절한 업데이트 주기를 선택하고 clearRect()로 이전 그림을 지워야 한다

완성된 시계 예제

모든 기능이 포함된 완전한 시계를 만들어보자.

<canvas id="canvas8" width="400" height="400" style="border:1px solid #d3d3d3;">
브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var canvas = document.getElementById("canvas8");
var ctx = canvas.getContext("2d");
var radius = canvas.height / 2;
ctx.translate(radius, radius);
radius = radius * 0.90;
setInterval(drawClock, 1000);
function drawClock() {
ctx.clearRect(-radius, -radius, radius * 2, radius * 2);
drawFace(ctx, radius);
drawNumbers(ctx, radius);
drawMarks(ctx, radius);
drawTime(ctx, radius);
}
function drawFace(ctx, radius) {
var grad;
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.fillStyle = '#f8f9fa';
ctx.fill();
grad = ctx.createRadialGradient(0, 0, radius * 0.95, 0, 0, radius * 1.05);
grad.addColorStop(0, '#2c3e50');
grad.addColorStop(0.5, '#34495e');
grad.addColorStop(1, '#2c3e50');
ctx.strokeStyle = grad;
ctx.lineWidth = radius * 0.1;
ctx.stroke();
ctx.beginPath();
ctx.arc(0, 0, radius * 0.1, 0, 2 * Math.PI);
ctx.fillStyle = '#e74c3c';
ctx.fill();
}
function drawNumbers(ctx, radius) {
var ang;
var num;
ctx.font = "bold " + radius * 0.15 + "px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "#2c3e50";
for (num = 1; num < 13; num++) { ang = num * Math.PI / 6; ctx.rotate(ang); ctx.translate(0, -radius * 0.85); ctx.rotate(-ang); ctx.fillText(num.toString(), 0, 0); ctx.rotate(ang); ctx.translate(0, radius * 0.85); ctx.rotate(-ang); } } function drawMarks(ctx, radius) { var ang; var mark; for (mark = 0; mark < 60; mark++) { ang = mark * Math.PI / 30; ctx.rotate(ang); ctx.translate(0, -radius * 0.93); ctx.rotate(-ang); if (mark % 5 === 0) { ctx.beginPath(); ctx.arc(0, 0, radius * 0.03, 0, 2 * Math.PI); ctx.fillStyle = '#2c3e50'; ctx.fill(); } else { ctx.beginPath(); ctx.arc(0, 0, radius * 0.015, 0, 2 * Math.PI); ctx.fillStyle = '#95a5a6'; ctx.fill(); } ctx.rotate(ang); ctx.translate(0, radius * 0.93); ctx.rotate(-ang); } } function drawTime(ctx, radius) { var now = new Date(); var hour = now.getHours(); var minute = now.getMinutes(); var second = now.getSeconds(); hour = hour % 12; hour = (hour * Math.PI / 6) + (minute * Math.PI / (6 * 60)) + (second * Math.PI / (360 * 60)); drawHand(ctx, hour, radius * 0.5, radius * 0.07, "#2c3e50"); minute = (minute * Math.PI / 30) + (second * Math.PI / (30 * 60)); drawHand(ctx, minute, radius * 0.8, radius * 0.07, "#34495e"); second = (second * Math.PI / 30); drawHand(ctx, second, radius * 0.9, radius * 0.02, "#e74c3c"); } function drawHand(ctx, pos, length, width, color) { ctx.save(); ctx.shadowColor = "rgba(0,0,0,0.2)"; ctx.shadowOffsetX = 2; ctx.shadowOffsetY = 2; ctx.shadowBlur = 2; ctx.beginPath(); ctx.lineWidth = width; ctx.lineCap = "round"; ctx.strokeStyle = color; ctx.moveTo(0, 0); ctx.rotate(pos); ctx.lineTo(0, -length); ctx.stroke(); ctx.restore(); } </script>[/code]

이 완성된 시계는 모든 기능을 포함하고 있다. 그림자 효과, 색상 조합, 시간 마크 등이 모두 적용되어 전문적인 시계를 만들 수 있다.

Canvas를 사용한 시계 제작은 웹 개발에서 그래픽 프로그래밍의 기초를 배우는 훌륭한 예제이다. 이 과정에서 배운 기법들은 다양한 시각화 프로젝트에 응용할 수 있다.

참고
Mingg`s Diary
밍구
공부 목적 블로그