'use strict'; // Analog clock with HTML5/Canvas // (c) Shingo Minagi 2020-2021 // https://www.minagi.jp/ var appSize = 300; var canvas, ctx; var pi = Math.PI; var starttime = null; var timezone=null; window.onload = function () { // 描画領域の大きさから盤面の直径を求める appSize = Math.min(window.innerWidth, window.innerHeight); // URLパラメータを取得する var search = decodeURI(location.search); // 「&」をデリミタに分割したトークンの配列 var searches; // searchesの各要素を「=」をデリミタに分割したトークンの配列 var key_value; // URLパラメータの解析 if (search) { search = search.replace('?', ''); searches = search.split('&'); for (var i in searches) { key_value = searches[i].split('='); switch (key_value[0].toLowerCase()) { case 'size': if (!isNaN(key_value[1])) { appSize = parseInt(key_value[1]); } break; case 'timezone': if (!isNaN(key_value[1])) { timezone = parseFloat(key_value[1]); } if(key_value[1]==null){ timezone=null; } break; default: break; } } } // DOMオブジェクトの生成 var div = document.createElement('div'); canvas = document.createElement('canvas'); ctx = canvas.getContext('2d'); div.style.position = 'relative'; canvas.id = 'board'; canvas.style.position = 'absolute'; canvas.style.borderRadius = Math.floor(appSize*0.21)+'px'; canvas.width = appSize; canvas.height = appSize; div.appendChild(canvas); document.body.appendChild(div); resize(); draw(); } // 描画領域がリサイズされた時のイベントハンドラ window.onresize = function () { resize(); } // 描画領域がリサイズされた時の処理 function resize() { canvas.style.width = window.innerWidth + 'px'; canvas.style.height = window.innerHeight + 'px'; } // 2次元座標(x,y)のオブジェクト var COORDINATES = function (_x, _y) { this.x = _x; this.y = _y; }; // 盤面の直径、角度(時計回り)、中心から外周までの長さに対する割合から // 盤面上の座標を計算する(盤面が真円である前提) function getCoordinates(size, degree, percent) { // 盤面の半径 var r = size / 2; // 時計の角度を三角関数の角度(反時計回り)に変換する var theta = (450 - degree) % 360; // 角度をラジアンに変換する var radian = theta / 180 * pi; // 三角関数 var cos = Math.cos(radian); var sin = Math.sin(radian); var x, y; x = r * (1 + cos * percent / 100); y = r * (1 - sin * percent / 100); return new COORDINATES(x, y); } // getCoordinatesで計算した座標に移動する function moveTo(context, size, degree, precent) { var coordinates = getCoordinates(size, degree, precent); context.moveTo(coordinates.x, coordinates.y); } // getCoordinatesで計算した座標へ直線を描く function lineTo(context, size, degree, precent) { var coordinates = getCoordinates(size, degree, precent); context.lineTo(coordinates.x, coordinates.y); } // 盤面・針の描画 function draw() { // 全体をクリアする ctx.clearRect(0, 0, appSize, appSize); // 角度 var deg; // 座標 var xy; // 周辺を黒色に塗る ctx.fillStyle = '#000000'; ctx.fillRect(0, 0, appSize, appSize); ctx.fillStyle = '#ffffff'; ctx.beginPath(); ctx.arc(appSize / 2, appSize / 2, appSize / 2 * 0.86, 0, 2 * pi); ctx.closePath(); ctx.fill(); // 盤面の描画 ctx.strokeStyle = '#000000'; for (var i = 0; i < 12; i++) { deg = i * 30; ctx.beginPath(); xy = getCoordinates(appSize, deg, 70); ctx.moveTo(xy.x, xy.y); ctx.fillStyle = '#000000'; ctx.font = 'bold '+Math.floor(appSize/12)+'px sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(i == 0 ? '12' : i, xy.x, xy.y, 128); ctx.closePath(); } // 針の描画 // 現在時刻を取得する if(starttime == null){ starttime = new Date().getTime(); } var currenttime = new Date().getTime(); var elapsedtime = currenttime - starttime; starttime = currenttime; var now = new Date(1715847978 + elapsedtime); if(timezone != null) { now = new Date(Date.now()+elapsedtime+new Date().getTimezoneOffset()*60*1000+timezone*60*60*1000); } var h = now.getHours(); var m = now.getMinutes(); var s = now.getSeconds(); var ms = now.getMilliseconds(); // 総秒数(0時0分0秒からの秒数) var seconds = h * 60 * 60 + m * 60 + s; // 時計の角度(中央上が0度) var deg_h = seconds / 86400 * 2 * 360; var deg_m = seconds / 3600 * 360; var deg_s = seconds % 60 * 6; var deg_ms = ms / 1000 * 6; // 時針 ctx.strokeStyle = '#000000'; ctx.lineWidth = appSize*0.01; // 1.0.1 ctx.beginPath(); moveTo(ctx, appSize, deg_h, 45); // 1.0.1 lineTo(ctx, appSize, deg_h , 2); ctx.stroke(); ctx.closePath(); ctx.lineCap='round'; ctx.lineWidth = appSize*0.025; // 1.0.1 ctx.beginPath(); moveTo(ctx, appSize, deg_h, 45); // 1.0.1 lineTo(ctx, appSize, deg_h , 10); // 1.0.1 ctx.stroke(); ctx.closePath(); ctx.lineCap='butt'; // 時針と分針の軸 ctx.lineWidth = appSize*0.01; ctx.beginPath(); ctx.arc(appSize/2,appSize/2,appSize/2*0.03,0,2*pi); // 1.0.1 ctx.stroke(); ctx.closePath(); // 分針 ctx.strokeStyle = '#000000'; ctx.lineWidth = appSize*0.01; ctx.beginPath(); moveTo(ctx, appSize, deg_m, 66); // 1.0.1 lineTo(ctx, appSize, deg_m % 360, 4); ctx.stroke(); ctx.closePath(); ctx.lineCap='round'; ctx.strokeStyle='#000000'; ctx.lineWidth = appSize*0.025; // 1.0.1 ctx.beginPath(); moveTo(ctx, appSize, deg_m, 66); // 1.0.1 lineTo(ctx, appSize, deg_m % 360, 10); // 1.0.1 ctx.stroke(); ctx.closePath(); ctx.lineCap='butt'; // 秒針 ctx.lineCap='round' ctx.strokeStyle = 'rgb(238,136,52)'; ctx.lineWidth = appSize*0.01; ctx.beginPath(); moveTo(ctx, appSize, deg_s + deg_ms, 78); lineTo(ctx, appSize, deg_s + deg_ms, 2.5); ctx.stroke(); ctx.closePath(); ctx.lineCap='round' ctx.beginPath(); moveTo(ctx, appSize, (deg_s + deg_ms + 180) % 360, 2.5); lineTo(ctx, appSize, (deg_s + deg_ms + 180) % 360, 8); ctx.stroke(); ctx.closePath(); ctx.lineCap='butt'; // 秒針の軸 ctx.lineCap='round' ctx.lineWidth = appSize*0.01; ctx.beginPath(); ctx.arc(appSize/2,appSize/2,appSize/2*0.02,0,2*pi); // 1.0.1 ctx.closePath(); ctx.stroke(); ctx.lineCap='butt'; // 30ミリ秒後に自分を呼び出す setTimeout(draw, 30); }