JavaScriptでアナログ時計を作る方法

JavaScriptでアナログ時計を作る方法 JavaScript

この記事では、JavaScriptアナログ時計を作る方法について説明をしていきたいと思います。
記事通りにコーディングをすることで以下のようなアナログ時計を作成することができます。

Sample Code

HTML

canvas要素を作成

ここではcanvas要素を幅・高さともに300pxにします。

<canvas width=300 height=300 id="clock"></canvas>

CSS

canvas要素の背景色を指定

canvas要素の領域が分かりやすいように背景色を設定します。

#clock {
  background-color: cornsilk;
}

JavaScript

canvas要素を取得

canvas要素を取得し、描画機能を有効にします。

const canvas = document.getElementById("clock");
const ctx = canvas.getContext('2d');

円の描画

canvasをクリアしたうえで、座標(150, 150)を中心とする半径150pxの円を描画します。

function drawBoard() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.beginPath();
  ctx.arc(150, 150, 150, 0, Math.PI * 2);
  ctx.lineWidth = 1.0;
  ctx.stroke();
}

現在時刻の取得

変数を宣言して、1秒置きに現在の日付や日時を代入します。

let d;
let year;
let month;
let date;
let day;
let dayArr = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
let hours;
let minutes;
let seconds;
let dateText;

function getTime() {
  d = new Date();
  year = d.getFullYear();
  month = d.getMonth() + 1;
  date = d.getDate();
  day = d.getDay();
  hours = d.getHours() - 12;
  minutes = d.getMinutes();
  seconds = d.getSeconds();
  setTimeout(getTime, 1000);
}

秒針を描画する関数の定義

円の中に秒針を作成します。moveTo()でパスを中心点である(150, 150)に移動させたうえで、lineTo()で指定した座標に線を引きます。
例えば、0秒の場合は、(150+140*cos270°, 150+140*sin270°)なので(150, 10)になり、
15秒の場合は、(150+140*cos360°, 150+140*sin360°)なので(290, 150)になります。

function sRotate() {
  ctx.beginPath();
  ctx.moveTo(150, 150);
  ctx.lineTo(150 + 140 * Math.cos(Math.PI / 180 * (270 + seconds * 6)), 150 + 140 * Math.sin(Math.PI / 180 * (270 + seconds * 6)));
  ctx.lineWidth = 1.0;
  ctx.stroke();
}

分針を描画する関数の定義

円の中に分針を作成します。moveTo()でパスを中心点である(150, 150)に移動させたうえで、lineTo()で指定した座標に線を引きます。
例えば、0分0秒の場合は、(150+130*cos270°, 150+130*sin270°)なので(150, 20)になり、
15分0秒の場合は、(150+130*cos360°, 150+130*sin360°)なので(280, 150)になります。
秒については、6*秒数/60の値が角度に加算されます。
例えば、15分30秒の場合は、(150+130*cos363°, 150+130*sin363°)となります。

function mRotate() {
  ctx.beginPath();
  ctx.moveTo(150, 150);
  ctx.lineWidth = 3.0;
  ctx.lineTo(150 + 130 * Math.cos(Math.PI / 180 * (270 + 6 * (minutes + seconds / 60))), 150 + 130 * Math.sin(Math.PI / 180 * (270 + 6 * (minutes + seconds / 60))));
  ctx.stroke();
}

短針の作成

円の中に短針を作成します。moveTo()でパスを中心点である(150, 150)に移動させたうえで、lineTo()で指定した座標に線を引きます。
例えば、0時0分の場合は、(150+100*cos270°, 150+100*sin270°)なので(150, 20)になり、
3時0分の場合は、(150+100*cos360°, 150+100*sin360°)なので(250, 150)になります。
分については、30*分数/60の値が角度に加算されます。
例えば、3時30分の場合は、(150+100*cos375°, 150+100*sin375°)となります。

function hRotate() {
  ctx.beginPath();
  ctx.moveTo(150, 150);
  ctx.lineWidth = 6.0;
  ctx.lineTo(150 + 100 * Math.cos(Math.PI / 180 * (270 + 30 * (hours + minutes / 60))), 150 + 100 * Math.sin(Math.PI / 180 * (270 + 30 * (hours + minutes / 60))));
  ctx.stroke();
}

秒針・分針・短針を呼び出す関数の定義

function rotate() {
  sRotate();
  mRotate();
  hRotate();
}

目盛りを作成する関数の定義

ここでは、目盛りの作成をする関数を定義します。
1つ目のfor文のループの中では1秒毎に刻む目盛りを作成します。
まずはmoveTo()で円と接している点に移動します。
そしてlineTo()で線を指定した座標に向かって引きます。
例えば、0秒を指す目盛りの場合は、moveTo()でパスを(150+150*0, 150+150*-1)つまり(150, 0)の位置に移動させ、lineTo()で(150+145*0, 150+145*-1)つまり(150, 5)に線を引きます。
それを60秒分繰り返すという訳ですね。
2つ目のfor分については、5秒毎に線を少し長く太くした形で引くという内容になっていますね。
なので線の開始位置であるmoveTo()の引数は1つ目のfor分と同じ内容になります。
lineTo()の引数については、cosとsinの引数を6°刻みにするか30°刻みにするかで変わるのと、
終了位置の座標を2番目のfor文については少し中心に近い位置にしています。

function drawScale() {
  for(let l = 0; l < 60; l++) {
    ctx.beginPath();
    ctx.moveTo(150 + 150 * Math.cos(Math.PI / 180 * (270 + l * 6)), 150 + 150 * Math.sin(Math.PI / 180 * (270 + l * 6)));
    ctx.lineTo(150 + 145 * Math.cos(Math.PI / 180 * (270 + l * 6)), 150 + 145 * Math.sin(Math.PI / 180 * (270 + l * 6)));
    ctx.lineWidth = 0.5;
    ctx.stroke();
  }
  for(let m = 0; m < 12; m++) {
    ctx.beginPath();
    ctx.moveTo(150 + 150 * Math.cos(Math.PI / 180 * (270 + m * 30)), 150 + 150 * Math.sin(Math.PI / 180 * (270 + m * 30)));
    ctx.lineTo(150 + 140 * Math.cos(Math.PI / 180 * (270 + m * 30)), 150 + 140 * Math.sin(Math.PI / 180 * (270 + m * 30)));
    ctx.lineWidth = 1.0;
    ctx.stroke();
  }
}

表示する文字を作成する関数の定義

ここではどこにどんなテキストを表示するかを指定します。
drawText()の中では、変数dateTextに年月日と曜日を代入します。
for文の中では、1から12の数字を配列に指定した位置に配置するという処理がされます。
関数branchAmPm()は午前か午後かを分けるという内容になっていますね。

function branchAmPm() {
  if(hours > 12) {
    return "P M";
  } else {
    return "A M";
  }
}

function drawText() {
  dateText = `${year}-${("0" + month).slice(-2)}-${("0" + date).slice(-2)} ${dayArr[day]}`;
  ctx.font = "30px 'MS ゴシック'";
  ctx.textAlign = "center";
  textArrX = [210, 255, 275, 260, 215, 150, 90, 45, 25, 45, 85, 150];
  textArrY = [55, 100, 160, 225, 270, 285, 270, 220, 160, 100, 55, 35];
  for(let i = 0; i <= 11; i++) {
    ctx.fillText(i+1 , textArrX[i], textArrY[i]);
  }
  ctx.font = "15px 'MS ゴシック'";
  ctx.fillText(dateText, 150, 80);
  ctx.fillText(branchAmPm(), 150, 100);
}

描画する関数を呼び出す関数の定義

あとは上の描画するための関数を1つにまとめます。

function draw() {
  drawBoard();
  drawScale();
  drawText();
  rotate();
  setTimeout(draw, 1000);
}

時間を取得する関数と描画をする関数の実行

最後に現在時刻を取得する関数getTime()とdraw()を呼び出せばいいですね。

getTime();
draw();

完成形

最終的には以下のような内容のコードになります。

・JavaScript

const canvas = document.getElementById("clock");
const ctx = canvas.getContext('2d');
let d;
let year;
let month;
let date;
let day;
let dayArr = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
let hours;
let minutes;
let seconds;
let dateText;

function sRotate() {
  ctx.beginPath();
  ctx.moveTo(150, 150);
  ctx.lineTo(150 + 140 * Math.cos(Math.PI / 180 * (270 + seconds * 6)), 150 + 140 * Math.sin(Math.PI / 180 * (270 + seconds * 6)));
  ctx.lineWidth = 1.0;
  ctx.stroke();
}

function mRotate() {
  ctx.beginPath();
  ctx.moveTo(150, 150);
  ctx.lineWidth = 3.0;
  ctx.lineTo(150 + 130 * Math.cos(Math.PI / 180 * (270 + 6 * (minutes + seconds / 60))), 150 + 130 * Math.sin(Math.PI / 180 * (270 + 6 * (minutes + seconds / 60))));
  ctx.stroke();
}

function hRotate() {
  ctx.beginPath();
  ctx.moveTo(150, 150);
  ctx.lineWidth = 6.0;
  ctx.lineTo(150 + 100 * Math.cos(Math.PI / 180 * (270 + 30 * (hours + minutes / 60))), 150 + 100 * Math.sin(Math.PI / 180 * (270 + 30 * (hours + minutes / 60))));
  ctx.stroke();
}

function rotate() {
  sRotate();
  mRotate();
  hRotate();
}

function branchAmPm() {
  if(hours >= 0) {
    return "P M";
  } else {
    return "A M";
  }
}

function drawText() {
  dateText = `${year}-${("0" + month).slice(-2)}-${("0" + date).slice(-2)} ${dayArr[day]}`;
  ctx.font = "30px 'MS ゴシック'";
  ctx.textAlign = "center";
  textArrX = [210, 255, 275, 260, 215, 150, 90, 45, 25, 45, 85, 150];
  textArrY = [55, 100, 160, 225, 270, 285, 270, 220, 160, 100, 55, 35];
  for(let i = 0; i <= 11; i++) {
    ctx.fillText(i+1 , textArrX[i], textArrY[i]);
  }
  ctx.font = "15px 'MS ゴシック'";
  ctx.fillText(dateText, 150, 80);
  ctx.fillText(branchAmPm(), 150, 100);
}

function drawScale() {
  for(let l = 0; l < 60; l++) {
    ctx.beginPath();
    ctx.moveTo(150 + 150 * Math.cos(Math.PI / 180 * (270 + l * 6)), 150 + 150 * Math.sin(Math.PI / 180 * (270 + l * 6)));
    ctx.lineTo(150 + 145 * Math.cos(Math.PI / 180 * (270 + l * 6)), 150 + 145 * Math.sin(Math.PI / 180 * (270 + l * 6)));
    ctx.lineWidth = 0.5;
    ctx.stroke();
  }
  for(let m = 0; m < 12; m++) {
    ctx.beginPath();
    ctx.moveTo(150 + 150 * Math.cos(Math.PI / 180 * (270 + m * 30)), 150 + 150 * Math.sin(Math.PI / 180 * (270 + m * 30)));
    ctx.lineTo(150 + 140 * Math.cos(Math.PI / 180 * (270 + m * 30)), 150 + 140 * Math.sin(Math.PI / 180 * (270 + m * 30)));
    ctx.lineWidth = 1.0;
    ctx.stroke();
  }
}

function drawBoard() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.beginPath();
  ctx.arc(150, 150, 150, 0, Math.PI * 2);
  ctx.lineWidth = 1.0;
  ctx.stroke();
}

function draw() {
  drawBoard();
  drawScale();
  drawText();
  rotate();
  setTimeout(draw, 1000);
}

function getTime() {
  d = new Date();
  year = d.getFullYear();
  month = d.getMonth() + 1;
  date = d.getDate();
  day = d.getDay();
  hours = d.getHours() - 12;
  minutes = d.getMinutes();
  seconds = d.getSeconds();
  setTimeout(getTime, 1000);
}

getTime();
draw();

コメント

タイトルとURLをコピーしました