CSSで時計を作ってみた

CSSだけで時計、とちょっと雑談。の記事を拝見し、いてもたってもいられなくなったので、同じようにCSSで時計を作ってみました。この時計は現在の時刻を指しているわけでも、実際の時計と同じ速度で動いているわけでもありませんのでご注意ください。

CSSでデザインした時計

12
3
6
9

HTMLマークアップ

HTMLマークアップは下記のようになっています。

12
3
6
9

このHTMLマークアップについて

HTML構造は参考にさせていただいたページとほぼ同じですが、クラス名の付け方を変えています。元記事のnegic様のクラス名(ID名)の付け方が素晴らしく、CSSセレクタの書き方も元記事の方が通好み(変態好み)の仕様だと思います。

当記事では、12・3・6・9の時字(ときじ)をHTMLに記述しました。時字の部分を「番号付きリストにしてやろうか?」「nth-childでセレクトしてやろうか?」などと妄想しすぎて、集中が途切れてしまうのを避けるため、直に書くというベタな方法を選択したわけで、ベタ書きです。

CSS

CSSは下記のようになりました。

negic様の記事は2012年末に書かれているため、ベンダープレフィックスを丁寧に設定されています。すでに多くのブラウザでベンダープレフィックスが不要になって来ましたので当記事では付けていません。 本家のコードとは細かな部分で差がありますが、肝の部分はほぼ一緒です。

なお、このページではHTML5 Reset StylesheetのリセットCSSが有効になっています。 そのため、リセットCSSを使用していないページや、別のリセットCSS・ノーマライズCSSを使用しているページで実践した場合に、同じ見た目にならない可能性があります。

.cssClock {
	width: 200px;
	height: 200px;
	background: #EEE;
	border-radius: 50%;
	position: relative;
}

.cssClock__center,
.cssClock__hourMark {
	position: absolute;
	top : 0;
	right: 0;
	bottom: 0;
	left: 0;
	margin: auto;
	width: 50px;
	height: 50px;
	border-radius: 50%;
	line-height: 50px;
	text-align: center;
	font-size: 20px;
	font-weight: bold;
	color: #000;
}

.cssClock__center {
	background: rgba(0,0,0,0.2);
	border: 10px double rgba(255,255,255,1);
	width: 30px;
	height: 30px;
}

.cssClock__center:after {
	display: block;
	content: "";
	width: 10px;
	height: 10px;
	background: #000;
	border-radius: 50%;
}

.cssClock__hourMark--00 { bottom: auto; }
.cssClock__hourMark--15 { left: auto; }
.cssClock__hourMark--30 { top: auto; }
.cssClock__hourMark--45 { right: auto; }

.cssClock__secondHand,
.cssClock__minuteHand,
.cssClock__hourHand {
	height: 50%;
	width: 50%;
	position: absolute;
	top: 50%;
	left: 50%;
	transform-origin: 0 0 0;
	border-left: 1px solid rgba(0,0,0,0.5);
}

.cssClock__hourHand {
	animation: clock calc( 360 / 0.5 * 1s / 10 ) linear 0s infinite normal;
	animation: clock 72s linear 0s infinite normal;
	height: 25%;
}

.cssClock__minuteHand {
	animation: clock calc( 360 / 6 * 1s / 10 ) linear 0s infinite normal;
	animation: clock 6s linear 0s infinite normal;
	height: 45%;
}

.cssClock__secondHand {
	animation: clock calc( 360 / 60 * 1s / 10 ) linear 0s infinite normal;
	animation: clock 0.6s linear 0s infinite normal;
	display: none;
}

@keyframes clock {
	0% { transform: rotate(180deg); }
	100% { transform: rotate(540deg); }
}

一部CSSの解説や補足

CSSを全行解説するのは非常に手間がかかるので、一部だけ解説・補足します。何か一部分だけでも参考になりましたら幸いです。

要素を天地中央に配置する

以下の部分は、要素を上下左右にセンタリング(天地中央に配置)する方法の1つです。
positionプロパティの上下左右の値を0にし、margin: auto;を設定します。

配置したい要素の幅や高さも忘れずに指定しておきましょう。

.cssClock__center,
.cssClock__hourMark {
	position: absolute;
	top : 0;
	right: 0;
	bottom: 0;
	left: 0;
	margin: auto;
	width: 50px;
	height: 50px;
	border-radius: 50%;
	line-height: 50px;
	text-align: center;
	font-size: 20px;
	font-weight: bold;
	color: #000;
}

上記のコードで天地中央に配置された要素は、上下左右のいずれかの値をautoに変更するだけで、親要素(もしくは先祖要素)の上下左右の辺の中点にくっ付くように配置できます。12・3・6・9の時字(ときじ)の配置にはもってこいでした。

.cssClock__hourMark--00 { bottom: auto; }
.cssClock__hourMark--15 { left: auto; }
.cssClock__hourMark--30 { top: auto; }
.cssClock__hourMark--45 { right: auto; }

calcでアニメーション速度を計算

それぞれの時計の針のアニメーション速度をcalcで計算していますが、Chrome以外のブラウザでは正しい結果が得られずに動作しませんでした。仕方なく電卓で計算した結果を次の行に記述しています。いろんな書き方を試しましたが、どうにもこうにも、解決できませんでした。

animation-durationにはcalcで計算した値を渡せない仕様なのか、Chrome以外のブラウザの不具合なのか、Chromeの不具合で動作してしまっているのか、わかりません。

.cssClock__hourHand {
	animation: clock calc( 360 / 0.5 * 1s / 10 ) linear 0s infinite normal;
	animation: clock 72s linear 0s infinite normal;
	height: 25%;
}

今回のCSSの問題点(時計の中心がずれる)

下記のコードは時計の中心にある黒い丸の部分ですが、これは恥ずかしいところを隠すために追加したコードです。

.cssClock__center:after {
	display: block;
	content: "";
	width: 10px;
	height: 10px;
	background: #000;
	border-radius: 50%;
}

時計の中心部分が何故に恥ずかしいところなのか・・・・・・

当記事では秒針・長針・短針を1ピクセルのボーダーで描いてleft: 50%;で配置していますが、その線は中心から右に1ピクセルずれた位置に描画されます。

.cssClock__secondHand,
.cssClock__minuteHand,
.cssClock__hourHand {
	height: 50%;
	width: 50%;
	position: absolute;
	top: 50%;
	left: 50%;
	transform-origin: 0 0 0;
	border-left: 1px solid rgba(0,0,0,0.5);
}

中心から右に1ピクセルずれた場所に描画されるのであれば、時計自体の幅(.cssClock)を奇数の値にすれば上手く中心に1ピクセルのボーダーを配置できるのかと思いきや、どちらにせよ、ページを表示しているブラウザの横幅を変えると、1ピクセルのズレが生じてしまうようでした。

つまり、時計の針の根本を時計の中心点にぴったり重ねたいなら、left: 50%;のように水平方向の位置をパーセント単位で指定してはいけないのです。恥ずかしい。

時計自体の幅を奇数にして、水平方向の位置をピクセル単位で指定すれば、針が中心部分で重なるように表示できますが、今度は、12・3・6・9の時字(ときじ)の配置にズレが生じるようになります。

時計の針の根本同士を中心部分でぴったり重ねる場合は、奇数の幅をもった要素の中で、ピクセル単位で位置指定する必要があり、 時字(ときじ)を親要素の上下左右の辺の中点にくっ付くように重ねたい場合は、偶数の幅をもった要素の中でパーセント単位で指定する必要があり・・・・・・ そもそも、円の中心点を1ピクセルで描画したいなら、円の直径は奇数にしたほうがいいとか、中心点を描画するという話になってしまい、CSSで時計を作るという話から遠ざかってしまいます。

中心点からの1ピクセルのずれに悩まされないためには、以下のようにしたほうが良さそうです。

  • 時計自体の幅は奇数の値でピクセル指定にする
  • 針の要素の水平方向の位置はピクセル単位で指定する
  • 時字(ときじ)の位置もピクセル単位で指定する

ちなみに、秒針が見えなくても安心してください。あまりにも回転が早過ぎるのでdisplay: none;していますので見えるはずがありません。

最後に

とてもよい勉強になりました。元ページでは30分で書かれたということでしたが、なんだかんだ調べながらやってるうちに2時間はかかったと思います。CSSの変態にはほど遠いなと痛感した次第です。

以下は参考にさせていただいたページです。ありがとうございました。

参考:
CSSだけで時計、とちょっと雑談。
時計算 - Wikipedia
腕時計の各部の名称を教えて
テクセルからピクセルへの直接的なマッピング (Direct3D 9)