アナログ時計 Analog Clock

JavaScriptでデジタル表記付きのアナログ時計を実装する方法をご紹介します。

  • ※ CSS trasformによって回転させています。
  • ※ IE9以下には対応していません。

デモ

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12

実装方法

HTML

<div class="analog-clock">
	<div class="movement-board">
		<ol class="dial">
			<li aria-label="five past">1</li>
			<li aria-label="ten past">2</li>
			<li aria-label="a quarter past">3</li>
			<li aria-label="twenty past">4</li>
			<li aria-label="twenty five past">5</li>
			<li aria-label="half past">6</li>
			<li aria-label="twenty five to">7</li>
			<li aria-label="twenty to">8</li>
			<li aria-label="quarter to">9</li>
			<li aria-label="ten to">10</li>
			<li aria-label="five to">11</li>
			<li aria-label="o'clock">12</li>
		</ol>
		<p class="hands">
			<span class="hour-hand"></span>
			<span class="minute-hand"></span>
			<span class="second-hand"></span>
			<span class="cap"></span>
		</p>
		<p class="digital-time">
			<span class="hour"></span>
			<span class="minute"></span>
			<span class="second"></span>
		</p>
	</div>
</div>

CSS


div.analog-clock {
	width:250px;
	height:250px;
}

	div.analog-clock div.movement-board {
		position:relative;
		box-sizing:border-box;
		border:2px solid #666;
		border-radius:50%;
		width:100%;
		height:100%;
		background-color:#f5f5f5;
	}

		div.analog-clock div.movement-board ol.dial {
			margin:0;
			padding:0;
			position:absolute;
			top:50%;
			left:50%;
			z-index:1;
			list-style:none;
		}

			div.analog-clock div.movement-board ol.dial li {
				position:absolute;
				top:-0.5em;
				left:-0.25em;
				font-size:2rem;
				line-height:1em;
			}

			div.analog-clock div.movement-board ol.dial li:nth-last-child(1) {
				transform:translate(52.5px, -90.93px);
			}

			div.analog-clock div.movement-board ol.dial li:nth-last-child(-n+3) {
				left:-0.5em;
			}

		div.analog-clock div.movement-board p.hands {
			margin:0;
			position:absolute;
			top:50%;
			left:50%;
			z-index:3;
		}

			div.analog-clock div.movement-board p.hands span.hour-hand {
				display:block;
				position:absolute;
				left:-2px;
				bottom:0;
				width:5px;
				height:70px;
				background-color:#ea3f8d;
				transform-origin:50% bottom;
			}

			div.analog-clock div.movement-board p.hands span.minute-hand {
				display:block;
				position:absolute;
				left:-1px;
				bottom:0;
				width:3px;
				height:90px;
				background-color:#ea3f8d;
				transform-origin:50% bottom;
			}

			div.analog-clock div.movement-board p.hands span.second-hand {
				display:block;
				position:absolute;
				left:0;
				bottom:0;
				width:1px;
				height:115px;
				background-color:#ea3f8d;
				transform-origin:50% bottom;
			}

			div.analog-clock div.movement-board p.hands span.cap {
				display:block;
				position:absolute;
				left:-10px;
				bottom:-10px;
				box-sizing:border-box;
				border:1px solid #888;
				border-radius:50%;
				width:20px;
				height:20px;
				background-color:#fff;
			}

		div.analog-clock div.movement-board p.digital-time {
			position:absolute;
			bottom:25%;
			left:50%;
			z-index:2;
			margin:0;
			padding:2px;
			min-width:100px;
			background-color:#fff;
			box-shadow:inset 1px 1px 1px 0 rgba(0, 0, 0, 0.2);
			transform:translate(-50%, 0);
			text-align:center;
			letter-spacing:-1em;
		}

			div.analog-clock div.movement-board p.digital-time span {
				display:inline-block;
				letter-spacing:0;
			}

			div.analog-clock div.movement-board p.digital-time span.hour {}

			div.analog-clock div.movement-board p.digital-time span.hour::after {
				content:":";
			}

			div.analog-clock div.movement-board p.digital-time span.minute {}

			div.analog-clock div.movement-board p.digital-time span.minute::after {
				content:":";
			}

			div.analog-clock div.movement-board p.digital-time span.second {}

JavaScript

(function() {
	/**
	 * 数値のゼロ埋め(桁を揃える)
	 * @param {number|string} number 対象の数字
	 * @param {number} digit 桁数
	 * @return {string} ゼロが埋められた数字を返す
	 */
	var zeroPadding = function(number, digit) {
		var numberLength = String(number).length;

		if (digit > numberLength) {
			return (new Array((digit - numberLength) + 1).join(0)) + number;
		} else {
			return number;
		}
	};

	window.addEventListener('DOMContentLoaded', function() {
		var stageElem       = document.querySelector('.analog-clock'),
		    boardElem       = stageElem.querySelector('.movement-board'),
		    dialsElem       = boardElem.querySelector('.dial'),
		    dialItemElems   = dialsElem.querySelectorAll('li'),
		    handsElem       = boardElem.querySelector('.hands'),
		    hourHandElem    = handsElem.querySelector('.hour-hand'),
		    minuteHandElem  = handsElem.querySelector('.minute-hand'),
		    secondHandElem  = handsElem.querySelector('.second-hand'),
		    digitalTimeElem = boardElem.querySelector('.digital-time'),
		    timeHourElem    = digitalTimeElem.querySelector('.hour'),
		    timeMinuteElem  = digitalTimeElem.querySelector('.minute'),
		    timeSecondElem  = digitalTimeElem.querySelector('.second'),
		    radius          = stageElem.clientWidth / 2 - 20, // 半径 - 内側に寄せる値
		    smoothRotate    = false; // スムーズに秒針を動かすかどうか

		// ダイヤルの配置
		Array.prototype.forEach.call(dialItemElems, function(element, index) {
			var angle  = (index + 1) * 30,
			    radian = (angle - 90) / 180 * Math.PI,
			    x      = radius * Math.cos(radian),
			    y      = radius * Math.sin(radian);

			element.style.transform = 'translate(' + x.toFixed(2) + 'px, ' + y.toFixed(2) + 'px)';
		});

		/**
		 * レンダリング
		 */
		var render = function() {
			var dateObj          = new Date(),
			    timeHour         = dateObj.getHours(),
			    timeMinute       = dateObj.getMinutes(),
			    timeSeconds      = dateObj.getSeconds(),
			    timeMilliseconds = dateObj.getMilliseconds(),
			    hourHandAngle    = (timeHour / 12) * 360,
			    minuteHandAngle  = (timeMinute / 60) * 360;

			// スムーズに回転させるかどうか
			if (smoothRotate) {
				secondHandAngle = (Number((timeSeconds + (timeMilliseconds / 1000)).toFixed(3).replace('.', '')) / 60000) * 360;
			} else {
				secondHandAngle = (timeSeconds / 60) * 360;
			}

			// 針を回転
			hourHandElem.style.transform   = 'rotate(' + hourHandAngle + 'deg)';
			minuteHandElem.style.transform = 'rotate(' + minuteHandAngle + 'deg)';
			secondHandElem.style.transform = 'rotate(' + secondHandAngle + 'deg)';

			// デジタル表記を反映
			timeHourElem.textContent   = zeroPadding(timeHour, 2);
			timeMinuteElem.textContent = zeroPadding(timeMinute, 2);
			timeSecondElem.textContent = zeroPadding(timeSeconds, 2);
		};

		setInterval(render, 10);
		render();
	});
})();

1から12の数字要素をCSSのtransformプロパティのtranslate関数で円形に配置します。

render関数内では時分秒を取得し、それぞれの針要素を回転させています。
smoothRotate変数で切り替えられるようにしています。

JavaScript逆引きリファレンス一覧へ戻る