관련지식
javascript, canvas, counter

옛날 홈페이지엔 플래시를 이용한 방문 카운터, 시계등이 꽤 자주 보였던것 같은데 요새는 보기가 쉽지 않습니다. 굳이 필요한 내용도 아닌것 같은데 유행처럼 사용했던 거였죠. 이전에 ‘canvas 기초 사용법’ 에서 만들었던 샘플을 이용하여 옛날 감수성 떠올리며 시계를 만들어 보겠습니다.

동작 구조

만들어낼 화면은 아래와 같습니다.

시분초를 구분하는 : 은 시간에 따라 변하는 내용이 아니므로 화면이 처음 열릴때 한번만 그립니다. 시분초가 표시될 각 패널은 독립적으로 움직이고 서로 연관이 없습니다. 시계는 오직 Date 정보에 있는 값을 기준으로만 동작합니다.

매초 현재의 시분초를 구하고 그값을 각 패널에 세팅합니다. 패널은 값이 바뀔 경우에만 애니메이션 효과를 주고 새 값으로 보여줍니다. 값이 바뀌지 않는다면 다시 그리지 않습니다. 따라서 초는 매번 새로 그려질 것이고 분단위는 1분에 1번꼴로 다시 그려질 것입니다.

초기화

화면이 처음 실행되면 시계 정보를 만들어야 합니다. 그리고 : 도 그려줍니다.

  1. function init() {
  2. //시계 패널 만들기
  3. for(var i = 0; i < 6; i++) {
  4. var obj = number(i);
  5. clock.push(obj);
  6. }
  7. // : 그리기
  8. ctx.fillStyle = "#000";
  9. ctx.fillRect(textWidth * 2 + 12, textHalfHeight - 10, 5, 5);
  10. ctx.fillRect(textWidth * 2 + 12, textHalfHeight + 10, 5, 5);
  11. ctx.fillRect(textWidth * 4 + 32, textHalfHeight - 10, 5, 5);
  12. ctx.fillRect(textWidth * 4 + 32, textHalfHeight + 10, 5, 5);
  13. }
  14. function number(idx) {
  15. var x = textWidth * idx + (idx * 10); //패널의 위치(x좌표)
  16. var number = -1; //현재 보일 숫자
  17. }

현재 시간 구해서 세팅하기

현재시간을 구하는 방법은 다양하게 있습니다. 서버 호출을 통해 서버 시간을 구해도 될 것이고 moment.js 를 이용하여 클라이언트 시간을 편하게 구할수도 있을 것입니다.

시분초 값을 구해 각 위치에 맞는 패널에 세팅합니다.

  1. function drawTime() {
  2. var d = new Date().toTimeString().substring(0, 8).replace(/:/g, ''); //시분초 구하기
  3. for(var i = 0; i < 6; i++) {
  4. clock[i].setNumber(d[i]);
  5. }
  6. }

숫자 그리기

기존에 있던 값이 -1 이면 아직 세팅이 안된 상태므로 애니메이션 효과없이 바로 그립니다. 그러나 기존에 0 이상의 값이 세팅되어있고 그것과 다른 값이 넘어왔을 경우 시간이 바뀐 것이므로 애니메이션 효과를 적용합니다.

  1. setNumber : function(num) {
  2. num = parseInt(num + '', 10);
  3. if(number == -1) {
  4. drawNumber(num);
  5. }
  6. else if(number != num) {
  7. next(drawNumber.bind(null, num)); //애니메이션 효과 후 숫자 그리기
  8. }
  9. }

숫자와 배경패널을 그리는 함수는 ‘canvas 기초 사용법’ 에 있는 내용에서 상수를 변수화 시키고 함수를 나누었을 뿐입니다.

  1. //배경 패널 그리기
  2. function makePanel() {
  3. ctx.fillStyle = "#000";
  4. ctx.fillRect(x, 0, textWidth, textHeight);
  5. ctx.fillStyle = "#FFF";
  6. ctx.fillRect(x, textHalfHeight - 1, textWidth, 1);
  7. }
  8. //숫자 그리기
  9. function drawNumber(tmp) {
  10. makePanel();
  11. number = tmp;
  12. ctx.font = '52px serif';
  13. ctx.fillStyle = "#FFF";
  14. ctx.textAlign = 'center';
  15. ctx.textBaseline = 'middle';
  16. ctx.fillText(number, x + textHalfWidth, textHalfHeight);
  17. }

이제 매초 drawTime() 함수를 호출하면 됩니다. (최종 소스는 아래에 있습니다.)

정리

시간에 맞춰 화면을 숫자를 그리는게 전부다 보니 로직 보다도 캔버스를 다루는 내용이 더 깁니다. 그마저도 이전 게시물에서 다루다보니 별로 설명할게 없네요. 만약 좀 더 복잡한 이미지나 애니메이션을 다뤄야 한다면 canvas 를 그대로 이용하는 것보다 다른 라이브러리를 통해 사용하는것이 더 편하실 것입니다.

최종 소스

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1">
  6. <style>
  7. </style>
  8. </head>
  9. <body>
  10. <canvas id="canvas" width="400px"></canvas>
  11. <canvas id="tmpCanvas" style="display:none"></canvas>
  12. <script>
  13. var canvas = document.getElementById("canvas");
  14. var ctx = canvas.getContext("2d");
  15. var tmpCanvas = document.getElementById("tmpCanvas");
  16. var tmpCtx = tmpCanvas.getContext("2d");
  17. var textWidth = 50;
  18. var textHeight = 60;
  19. var textHalfWidth = textWidth / 2;
  20. var textHalfHeight = textHeight / 2;
  21. var clock = [];
  22. init();
  23. drawTime();
  24. setInterval(drawTime, 1000);
  25. function init() {
  26. //시계 패널 만들기
  27. for(var i = 0; i < 6; i++) {
  28. var obj = number(i);
  29. clock.push(obj);
  30. }
  31. // : 그리기
  32. ctx.fillStyle = "#000";
  33. ctx.fillRect(textWidth * 2 + 12, textHalfHeight - 10, 5, 5);
  34. ctx.fillRect(textWidth * 2 + 12, textHalfHeight + 10, 5, 5);
  35. ctx.fillRect(textWidth * 4 + 32, textHalfHeight - 10, 5, 5);
  36. ctx.fillRect(textWidth * 4 + 32, textHalfHeight + 10, 5, 5);
  37. }
  38. function drawTime() {
  39. var d = new Date().toTimeString().substring(0, 8).replace(/:/g, ''); //시분초 구하기
  40. for(var i = 0; i < 6; i++) {
  41. clock[i].setNumber(d[i]);
  42. }
  43. }
  44. function number(idx) {
  45. var x = textWidth * idx + (idx * 10); //패널의 위치(x좌표)
  46. var number = -1; //현재 보일 숫자
  47. //배경 패널 그리기
  48. function makePanel() {
  49. ctx.fillStyle = "#000";
  50. ctx.fillRect(x, 0, textWidth, textHeight);
  51. ctx.fillStyle = "#FFF";
  52. ctx.fillRect(x, textHalfHeight - 1, textWidth, 1);
  53. }
  54. //숫자 그리기
  55. function drawNumber(tmp) {
  56. makePanel();
  57. number = tmp;
  58. ctx.font = '52px serif';
  59. ctx.fillStyle = "#FFF";
  60. ctx.textAlign = 'center';
  61. ctx.textBaseline = 'middle';
  62. ctx.fillText(number, x + textHalfWidth, textHalfHeight);
  63. }
  64. //패널 넘어가는 효과
  65. function next(cb) {
  66. var self = this;
  67. var t = 1;
  68. var half = ctx.getImageData(x, 0, textWidth, textHalfHeight); //이미지의 윗부분만 가져오기
  69. var ani = setInterval(function() {
  70. if(t <= -1) {
  71. clearInterval(ani);
  72. ctx.setTransform(1, 0, 0, 1, 0, 0);
  73. cb();
  74. return;
  75. }
  76. t -= 0.4;
  77. ctx.setTransform(1, 0, 0, t, 0, (1 - t) * 10 * 3); //캔버스 모양 변형
  78. tmpCtx.putImageData(half, 0, 0); //임시 캔버스에 이미지 윗부분 추가
  79. ctx.drawImage(tmpCanvas, x, 0); //임시캔버스 내용을 가져오기
  80. }, 50);
  81. }
  82. makePanel();
  83. return {
  84. setNumber : function(num) {
  85. num = parseInt(num + '', 10);
  86. if(number == -1) {
  87. drawNumber(num);
  88. }
  89. else if(number != num) {
  90. next(drawNumber.bind(null, num)); //애니메이션 효과 후 숫자 그리기
  91. }
  92. }
  93. };
  94. }
  95. </script>
  96. </body>
  97. </html>