
HTML Canvas 시계 바늘
HTML Canvas를 사용하여 시계 바늘을 그리는 방법을 알아본다. 이 튜토리얼에서는 시침, 분침, 초침을 그리고 실시간으로 움직이게 하는 방법을 설명한다.
시계 바늘 기본 원리
시계 바늘을 그리기 위해서는 다음과 같은 원리를 이해해야 한다.
- 시침은 12시간을 기준으로 360도 회전한다 (1시간 = 30도)
- 분침은 60분을 기준으로 360도 회전한다 (1분 = 6도)
- 초침은 60초를 기준으로 360도 회전한다 (1초 = 6도)
- 시침은 분과 초에 따라 연속적으로 움직인다
기본 시계 바늘 그리기
먼저 고정된 시간의 시계 바늘을 그려보자.
<canvas id="canvas1" width="400" height="400" style="border:1px solid #d3d3d3;">브라우저가 HTML5 Canvas를 지원하지 않습니다.
</canvas>
<script>
var canvas = document.getElementById("canvas1");
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 hour = 10; var minute = 10; var second = 30; 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]
이 예제에서는 10시 10분 30초를 나타내는 고정된 시계 바늘을 그린다. drawTime() 함수에서 각 바늘의 각도를 계산하고, drawHand() 함수에서 실제 바늘을 그린다.
실시간 시계 바늘
현재 시간에 따라 움직이는 시계 바늘을 만들어보자.
<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;
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]
이 예제에서는 new Date()를 사용하여 현재 시간을 가져오고, setInterval()을 사용하여 1초마다 시계를 업데이트한다.
다양한 색상의 시계 바늘
각 바늘에 다른 색상을 적용해보자.
<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;
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, "#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]
이 예제에서는 시침을 진한 파란색, 분침을 회색, 초침을 빨간색으로 표시하여 각 바늘을 구분하기 쉽게 만들었다.
고급 스타일 시계 바늘
더 정교한 디자인의 시계 바늘을 만들어보자.
<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;
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)); drawHourHand(ctx, hour, radius); minute = (minute * Math.PI / 30) + (second * Math.PI / (30 * 60)); drawMinuteHand(ctx, minute, radius); second = (second * Math.PI / 30); drawSecondHand(ctx, second, radius); } function drawHourHand(ctx, pos, radius) { ctx.save(); ctx.rotate(pos); ctx.beginPath(); ctx.lineWidth = radius * 0.07; ctx.lineCap = "round"; ctx.strokeStyle = "#2c3e50"; ctx.moveTo(0, radius * 0.1); ctx.lineTo(0, -radius * 0.5); ctx.stroke(); ctx.restore(); } function drawMinuteHand(ctx, pos, radius) { ctx.save(); ctx.rotate(pos); ctx.beginPath(); ctx.lineWidth = radius * 0.05; ctx.lineCap = "round"; ctx.strokeStyle = "#34495e"; ctx.moveTo(0, radius * 0.1); ctx.lineTo(0, -radius * 0.8); ctx.stroke(); ctx.restore(); } function drawSecondHand(ctx, pos, radius) { ctx.save(); ctx.rotate(pos); ctx.beginPath(); ctx.lineWidth = radius * 0.02; ctx.lineCap = "round"; ctx.strokeStyle = "#e74c3c"; ctx.moveTo(0, radius * 0.15); ctx.lineTo(0, -radius * 0.9); ctx.stroke(); ctx.beginPath(); ctx.arc(0, -radius * 0.75, radius * 0.05, 0, 2 * Math.PI); ctx.fillStyle = "#e74c3c"; ctx.fill(); ctx.restore(); } </script>[/code]
이 예제에서는 각 바늘을 개별 함수로 분리하고, 초침에 작은 원을 추가하여 더 현실적인 시계 바늘을 만들었다. save()와 restore() 메서드를 사용하여 변환 상태를 관리한다.
그림자 효과가 있는 시계 바늘
시계 바늘에 그림자 효과를 추가해보자.
<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, "#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.3)"; ctx.shadowOffsetX = 2; ctx.shadowOffsetY = 2; ctx.shadowBlur = 3; 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 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, 50);
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(); var millisecond = now.getMilliseconds(); 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) + (millisecond * Math.PI / (30 * 1000)); 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]
이 예제에서는 밀리초를 포함하여 초침이 부드럽게 움직이도록 만들었다. setInterval()의 간격을 50밀리초로 줄여서 더 부드러운 애니메이션을 구현했다.
💡 시계 바늘 제작 팁:
• 각도 계산 시 분과 초의 영향을 고려하여 연속적인 움직임을 만들어야 한다
• save()와 restore() 메서드를 사용하여 변환 상태를 관리하면 코드가 더 깔끔해진다
• 초침의 부드러운 움직임을 위해 밀리초를 포함한 계산을 사용할 수 있다
• 성능을 고려하여 적절한 업데이트 주기를 선택해야 한다
디지털 시간 표시와 함께
아날로그 시계 바늘과 디지털 시간을 함께 표시해보자.
<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);
drawTime(ctx, radius);
drawDigitalTime(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, "#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); } function drawDigitalTime(ctx, radius) { var now = new Date(); var hour = now.getHours(); var minute = now.getMinutes(); var second = now.getSeconds(); var timeString = (hour < 10 ? "0" : "") + hour + ":" + (minute < 10 ? "0" : "") + minute + ":" + (second < 10 ? "0" : "") + second; ctx.font = radius * 0.1 + "px Arial"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillStyle = "#2c3e50"; ctx.fillRect(-radius * 0.35, radius * 0.55, radius * 0.7, radius * 0.15); ctx.fillStyle = "white"; ctx.fillText(timeString, 0, radius * 0.625); } </script>[/code]
이 마지막 예제에서는 아날로그 시계 바늘과 함께 디지털 시간 표시를 추가하여 두 가지 형태의 시간 표시를 동시에 제공한다.
Canvas를 사용한 시계 바늘 제작은 각도 계산, 삼각함수, 애니메이션의 실용적인 활용 예시이다. 이러한 기법들은 게이지, 다이얼, 회전 인터페이스 등 다양한 웹 애플리케이션에서 응용할 수 있다.