CSS 文字(数字)を円形に配置してアナログ時計作成 jQueryで自動配置も可能

数字を円形に配置して、アナログ時計を作成しましたので紹介いたします。
文字を円形に配置する方法、時間を取得し時計の針の角度を変更する方法を解説します。

jQueryを使用すれば、時計に限らず文字数が特定されていない文章・単語等でも円形に配置することが可能です。
また、文字の向きも水平方向、円の中心に向かって内向き方向、外向き方向のパターンも紹介しています。

過去にもアナログ時計を作成したのですが、この時は数字を使用せず、いたってシンプルなものでした。デジタル時計も作成していました。

数字(文字)は水平に配置したパターン

CODEPEN

「Run Pen」をクリックしてください。

See the Pen wall clock 1 by blue moon (@blue-moon) on CodePen.

HTMLファイル

<body>
  <div class="circle">
    <div class="inner"> <!-- 時計の中心部分であり、且つ数字配置の基準となる -->
      <div class="time one">Ⅰ</div>
      <div class="time two">Ⅱ</div>
      <div class="time three">Ⅲ</div>
      <div class="time four">Ⅳ</div>
      <div class="time five">Ⅴ</div>
      <div class="time six">Ⅵ</div>
      <div class="time seven">Ⅶ</div>
      <div class="time eight">Ⅷ</div>
      <div class="time nine">Ⅸ</div>
      <div class="time ten">Ⅹ</div>
      <div class="time eleven">Ⅺ</div>
      <div class="time twelve">Ⅻ</div>
      <div class="short"></div> <!-- 時計の短針 -->
      <div class="long"></div> <!-- 時計の長針 -->
    </div>
  </div>
</body>

ローマ数字特殊記号

この記事の上記のコードでは、HTML特殊文字が既にローマ数字変換されて表示されているかもしれませんが、正確にはCODEPENに記載のとおり下記のようになります。

数字ローマ数字HTML数字ローマ数字HTML数字ローマ数字HTML
1&#8544;5 &#8548; 9 &#8552;
2&#8545;6 &#8549; 10 &#8553;
3 &#8546; 7 &#8550; 11 &#8554;
4 &#8547; 8 &#8551; 12 &#8555;

CSSファイル

* {
  box-sizing: border-box;
}

body {
  color: #222;  /* 時計本体表面にガラス(又は、透明のアクリル板等)を被せるため、実際にはこれより薄い色で表示 */
  font-size: 20px;
}

.circle {
  position: relative;  /* 時計の中心部分であり、数字の配置基準 */
  margin: 20px auto;
  width: 255px;
  height: 255px;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 50%;
  border: 9px solid #898989;  /* バトルシップグレイ */
  background: #f3f3f3;
  box-shadow: 4px 4px 0px #bfbfbf,  /* ぼかしをきかせず、時計枠の右下方向部分の外側側面を作成 */
              inset 3px 3px 0px #e6e6e6,  /* 時計枠の左上方向部分の内側側面を作成 */
              inset 2px 4px 8px #555;  /* 時計枠の内側側面の時計内部に落ちる影 */
  filter: drop-shadow(2px 3px 6px #777);  /* ぼかしをきかせた、時計本体の影 */
}

.circle::before{  /* 時計表面のガラス */
  position: absolute;
  inset:0;  /* top: 0; left:0; bottom: 0; right: 0; をまとめて指定、時計枠部分を差し引く必要はない */
  content: "";
  border-radius: 50%;
  background: linear-gradient(135deg, rgba(247,247,247,0.5) 0%,rgba(209,232,255,0.1) 20%,rgba(255,255,255,0.7) 30%,rgba(224,239,255,0.1) 50%,rgba(255,255,255,0.7) 70%,rgba(209,232,255,0.2) 80%,rgba(247,247,247,0.3) 100%);  
  z-index: 10;
}

.inner {
  position: relative;
  transform: translate(2px, 2px);
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background-image: linear-gradient(135deg, #b0b0b0 0%, #b5b5b5 1%, #d9d9d9 20%, #efefef 48%, #d9d9d9 75%, #bcbcbc 100%);
  box-shadow: 1px 1px 2px 1px rgba(0,0,0,0.65), inset 2px 2px 4px 0px #f3f3f3;
  transform-style: preserve-3d;  /* 子要素の短針と長針をZ軸方向にマイナス移動させるため、3D空間に */
}

.time,
.long,
.short {
  position: absolute;
  inset: 0;
  line-height: 1.1;
  font-weight: 700;
}

.short,
.long {
  background: #808080;
  transform-origin: bottom center;
}

.short {
  top: calc(10px - 55px);
  left: calc(50% - 2px);
  width: 6px;
  height: 55px;
  transform: translateZ(-2px);  /* JSで上書きされるため記述不要、位置確認のため */
}

.long {
  top: calc(10px - 80px);
  left: calc(50% - 1px);
  width: 3px;
  height: 80px;
  transform: translateZ(-3px);  /* JSで上書きされるため記述不要、位置確認のため */
}

.one {
  transform: translate(50px, -86.6px);  /* 文字自体は水平を維持で回転はなし */
}

.two {
  transform: translate(86.6px, -50px);
}

.three {
  transform: translate(100px, 0);
}

.four {
  transform: translate(86.6px, 50px);
}

.five {
  transform: translate(50px, 86.6px);
}

.six {
  transform: translate(0px, 100px);
}

.seven {
  transform: translate(-50px, 80px);
}

.eight {
  transform: translate(-86.6px, 50px);
}

.nine {
  transform: translate(-100px, 0px);
}

.ten {
  transform: translate(-86.6px, -50px);
}

.eleven {
  transform: translate(-50px, -86.6px);
}

.twelve {
  transform: translate(0px, -100px);
}

円形に配置する手順

文字を水平方向のまま正確に円形に配置すべきか悩みぬいて考案した手順です。

  1. 円形に配置する文字を1文字ずつタグで囲む。
  2. 基準の親要素を作成し、一旦全ての文字を{position: absolute; inset: 0;}で1箇所に配置する。
  3. ここを起点に、それぞれX軸とY軸に移動させる。
    その移動距離計算に使用するのが三角関数です。

まさか、人生で三角関数が役に立つ機会が来るとは思いませんでした。

文字の移動距離の計算方法

12時、3時、6時、9時のX軸・Y軸への移動距離は、単に0もしくは円の半径(プラス or マイナス)となるため計算不要です。

求めるのべき値は、下記図のxとyの値です。懐かしい三角関数の計算です。
但し、時計の1時と2時の移動距離が分かれば、後の数字は上下左右対称に位置するため、単純に方向距離の値をプラスやマイナスに変えるだけで済みます。

trigonometric_function

例えば1時の場合のX軸の移動距離は、以下となります。
今回は半径が100pxで、1時の移動角度は30度で、三角形のθは60度です。
x = 100 × cosθ = 100 × 1/2 = 50

一方、Y軸の移動距離は、次の通り計算できます。
y = 100 × sinθ = 100 × √3/2 ≒ 86.60
但し、WEBサイト上では、Y軸方向に86.60px移動すると下方向に移動するため、マイナス方向に移動することをお忘れなく。

角度をcosθ、sinθに変換

検索すれば、変換表や変換ツール等は多数表示されると思います。
弧度法やラジアンや三角関数についての説明は、より詳しい専門の方にお譲りしたいと思います。

もし、時計以外で文章や単語を円形に配置したい場合は、その文字数によって角度及び移動距離が変わってくるため、手動計算する必要があります。はっきり言って面倒です。
そこで、次の時計のパターンでは、文字数をカウントし自動配置させる関数を作成してみました。

但し、処理速度等を考慮するならば、今回のように計算が容易なら、CSSで指定した方がよいのかもしれません(確証はできませんが)。

グラデーション

時計表面のガラス部分と中心部分にはグラデーションを設定しています。

長針と短針の3D空間によるZ軸移動

親要素にtransformプロパティを設定しているため、スタッキングコンテキストを構成しなくなり、この親要素の中ではz-indexが効きません。
この状態では、針が時計の中心の円(針を止める金具?)の下に隠れず、見栄えが悪くなります。
そこで、針をZ軸方向にマイナス移動させるため、この親要素直接の子要素である針を3D空間配置としています。

MDN CSS:カスケーディングスタイルシート 重ね合わせコンテキスト

JSファイル

const time = () => {  // 時刻表示の関数作成

  const now = new Date(); // 現在の日時を取得
  const h = now.getHours(); // 時間のみ取得
  const m = now.getMinutes(); // 分のみ取得
  const short = $('.short');
  const long = $('.long');
  
  // 短針の角度
  const shortDeg = h < 13 ? h * 30 + m / 60 * 30 : (h - 12) * 30 + m / 60 * 30; // if-else文を1行にした短縮記法
  short.css('transform', 'rotate(' + shortDeg + 'deg) translateZ(-1px)');  // 回転とZ軸のマイナス移動
  
  // 長針の角度
  const longDeg = m * 6;
  long.css('transform', 'rotate(' + longDeg + 'deg) translateZ(-2px)');  // Z軸のマイナス移動は、短針より奥に
  
  // 短針の影の向き
  if (shortDeg < 90) { // 12時から2時59分まで
    short.css('box-shadow', '0.7px 0px 1.5px #000'); // 回転する前の要素の右と下に影
  } else if (shortDeg < 180){
    short.css('box-shadow', '0.7px -0.7px 1.5px #000'); // 右と上に影
  } else if (shortDeg < 270){
    short.css('box-shadow', '-0.7px -0.7px 1.5px #000'); // 左と上に影
  } else {
    short.css('box-shadow', '-0.7px 0px 1.5px #000'); // 左と下に影
  }    

  // 長針の影の向き
  if (longDeg < 90) {
    long.css('box-shadow', '0.7px 0px 1.5px #000'); // 回転する前の要素の右と下に影
  } else if (longDeg < 180){
    long.css('box-shadow', '0.7px -0.7px 1.5px #000'); // 右と上に影
  } else if (longDeg < 270){
    long.css('box-shadow', '-0.7px -0.7px 1.5px #000'); // 左と上に影
  } else {
    long.css('box-shadow', '-0.7px 0px 1.5px #000'); // 左と下に影
  }    

};

time();  // 時刻表示の関数呼び出し

// 最初に時刻表示した後は、1分後に表示を変更する
const timeUpdate = () => {  // 1分後に時刻表示の関数を呼び出す関数作成
  time();  // 呼び出す関数
  setTimeout(timeUpdate, 60000);  // 60000ミリ秒 = 1分間隔
};

timeUpdate();  // 1分毎に、時刻表示変更する関数呼び出し

時間表示

1分間隔で時間を取得していますので、時刻取得のタイミングにより、実際の時間と1分以内の誤差が発生します。
より正確に表示したい場合は、setTimeoutの2番目の引数でミリ秒数を変更してください(パフォーマンスを考慮した上で)。

長針と短針の角度と影

移動角度については単純な計算ですので、説明不要の方はこの段落は読み飛ばしてください。
取得する時間は24時間表記であるため、短針については13時以降は12を減算する必要があります。
そして、1時間の移動角度(30度)に、1分の移動角度(30度を60分で除算)を加算しています。
この分の角度を加算するのを忘れないようにしてください。

以外と気を遣うのが、jQueryのcssメソッドです。クォーテーションの位置や空白の有無に注意して記述してください。

また、長針と短針それぞれにとても小さいですが影を付けています。
影の向きは、移動する前(12時の地点に針がある時)につけたものです。
実際の時計の針は回転すれば当然影の向きも変わるものですが、transform: rotate() ;では、影の向きも継承しますので、影の位置が不自然になってしまいます。

よって、中盤で少し冗長気味なコードになってしまいましたが、回転角度によって影の向きを変更しています。

数字(文字)は内向きに配置したパターン 円形配置計算を自動化

ローマ数字の時計では、こちらのパターンの方が断然多いように思います。
「V」の文字の下部が円の中心を向いてる方が、見た目的にバランスがいいのでしょうか。

CODEPEN

「Run Pen」をクリックしてください。

See the Pen wall clock 2 by blue moon (@blue-moon) on CodePen.

CSSファイル

変更箇所のみ抜粋しています。

.circle {
  /* 途中省略 */
  border: 9px solid #942343;  /* ガーネット */
  /* 途中省略 */
}

  /* 途中省略 */
.long {
  /* 途中省略 */
}

/* 以降、数字(文字)の配置を指定するCSSは記述なし */

JSファイル

  const long = $('.long');
  // ここから下を追加

  const cnt = $('.time').length;  // 円形配置する文字数をカウント
  const circleR = ($('.circle').height()) / 2;  // 円の半径
  const fontH = ($('.inner').height());  // 文字サイズ(移動距離の基準とする親要素の高さ)
  const dist = circleR - fontH + 1.5;  // 円の半径から文字サイズを差し引き、最後に移動距離を調整

  // テキスト(今回は時刻の数字)の位置をその数に応じて均等に配置
  $('.time').each(function(index) {
    const num = index + 1; // 要素の順番を取得、index番号は0から始まるため、1を加算
    const radX = Math.sin(360 / cnt * num * (Math.PI / 180)); // 角度に(Math.PI / 180)を乗算し、ラジアン単位に変換後、rcosθを計算
    const radY = Math.sin((90 - (360 / cnt * num)) * (Math.PI / 180));  // rsinθを計算、直角三角形の直角以外の2つの角度の和90度から差し引いて…後は同じ
    $(this).css('transform', 'translate(' + dist * radX + 'px, ' + -(dist * radY) + 'px) rotate(' + 360 / cnt * num + 'deg)');
  });

  // ここまで追加
 const shortDeg = h < 13 ? h * 30 + m / 60 * 30 : (h - 12) * 30 + m / 60 * 30;

Meth.sin()メソッド

先述のパターンで出てきた、数字(文字)のX軸・Y軸への移動距離、即ちrconθ及びrsinθの計算に重要なメソッドです。

Mathオブジェクトのsin()メソッドは、サイン(正弦)を返します。 引数にラジアン単位の角度を指定すると、指定した引数のサイン(正弦)を返します。
ラジアンとは、弧度法によって表す角度の単位です。 具体的には、半径1の正円から切り出した円弧の長さからその中心角を特定します。

HTMLクイックリファレンス > JavaScriptリファレンス より引用

変数に格納した角度と、引用ページのJavaScriptソースを参考に、X軸とY軸の移動距離を算出しました。

each()メソッドとindex()メソッド

この2つのjQeryのメソッドにより、各数字毎にその順番に応じた角度を計算し、cssメソッドで角度を移動しています。

このcssメソッドですが、これに変数が加わると大変見づらいです。
空白の有無とクォーテーションの位置には十分注意してください。
今回は、文字の配置とともに文字の角度も内向きに回転していますが、水平を維持したい場合は、rotateプロパティの部分は省いてください。

文字の回転角度のみ、CSSのtransformプロパティで記述したいと思うことがあるかもしれません。
しかし、JSファイルに記載したこのメソッドにより、CSSのtransformプロパティの値は上書きされてしまいます。
よってJSファイルで、このプロパティの値全てを長々と記述しているのです。

数字(文字)は外向きに配置したパターン

CODEPEN

「Run Pen」をクリックしてください。

See the Pen wall clock 3 by blue moon (@blue-moon) on CodePen.

CSSファイル

変更箇所のみ抜粋しています。

.circle {
  /* 途中省略 */
  border: 9px solid #192f60; /* アイアンブルー */
  /* 途中省略 */
}

/* 途中省略 */

.one {
  transform: translate(50px, -86.6px) rotate(-150deg);  /* 外向き */
}

.two {
  transform: translate(86.6px, -50px) rotate(-120deg);
}

.three {
  transform: translate(100px, 0) rotate(-90deg);
}

.four {
  transform: translate(86.6px, 50px) rotate(-60deg);
}

.five {
  transform: translate(50px, 86.6px) rotate(-30deg);
}

.six {
  transform: translate(0px, 100px);
}

.seven {
  transform: translate(-50px, 80px) rotate(30deg);
}

.eight {
  transform: translate(-86.6px, 50px) rotate(60deg);
}

.nine {
  transform: translate(-100px, 0px) rotate(90deg);
}

.ten {
  transform: translate(-86.6px, -50px) rotate(120deg);
}

.eleven {
  transform: translate(-50px, -86.6px) rotate(150deg);
}

.twelve {
  transform: translate(0px, -100px) rotate(180deg);
}

【おまけ】時計以外で文字を円形に配置

CODEPEN

「Run Pen」をクリックしてください。
2つ目の時計のパターンと同じく、JSファイルで文字を円形に配置し、回転させてみました。
Googleのアニメーションフォントを利用し、3Dっぽくしています。

See the Pen circle_text by blue moon (@blue-moon) on CodePen.

話はそれて文字の円形配置とは異なりますが、どう実装すべきか悩んだ時、テキストがシェイプに沿って配置される shape-outside というプロパティを見つけました。
全く知りませんでしたが、今後使ってみたいと思います。

MDN CSS:カスケーディングスタイルシート CSS シェイプの概要

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