さぁ!検索しよう!

HTML,CSS,JavaScriptによる猫のアナログ時計の作り方です。

Demo

以下が今回作るものです。

秒針の移動と同時に猫が瞬きしている様子が確認できます。

HTMLで全体の構造を定義

始めに以下のHTMLを書いて全体の骨組みを作ります。

<div class="clock">
  <div class="clock__inner">
    <ul class="clock__nums">
      <li class="clock__num clock__num--1"><span class="clock__num__text 

clock__num__text--1">1</span></li>
      <li class="clock__num clock__num--2"><span class="clock__num__text 

clock__num__text--2">2</span></li>
      <li class="clock__num clock__num--3"><span class="clock__num__text 

clock__num__text--3">3</span></li>
      <li class="clock__num clock__num--4"><span class="clock__num__text 

clock__num__text--4">4</span></li>
      <li class="clock__num clock__num--5"><span class="clock__num__text 

clock__num__text--5">5</span></li>
      <li class="clock__num clock__num--6"><span class="clock__num__text 

clock__num__text--6">6</span></li>
      <li class="clock__num clock__num--7"><span class="clock__num__text 

clock__num__text--7">7</span></li>
      <li class="clock__num clock__num--8"><span class="clock__num__text 

clock__num__text--8">8</span></li>
      <li class="clock__num clock__num--9"><span class="clock__num__text 

clock__num__text--9">9</span></li>
      <li class="clock__num clock__num--10"><span class="clock__num__text 

clock__num__text--10">10</span></li>
      <li class="clock__num clock__num--11"><span class="clock__num__text 

clock__num__text--11">11</span></li>
      <li class="clock__num clock__num--12"><span class="clock__num__text 

clock__num__text--12">12</span></li>
    </ul>
    <div class="clock__needles">
      <div class="clock__needle clock__needle--hour" id="hour"></div>
      <div class="clock__needle clock__needle--min" id="min"></div>
      <div class="clock__needle clock__needle--sec" id="sec"></div>
    </div>
  </div>
  <div class="cat">
    <div class="cat__ears">
      <div class="cat__ears__ear cat__ears__ear--left">
      </div>
      <div class="cat__ears__ear cat__ears__ear--right">
      </div>
    </div>
    <div class="cat__whiskers">
      <div class="cat__whiskers__whisker cat__whiskers__whisker--left">
        <span></span>
      </div>
      <div class="cat__whiskers__whisker cat__whiskers__whisker--right">
        <span></span>
      </div>
    </div>
    <div class="cat__face">
      <div class="cat__face__pattern cat__face__pattern--top">
      </div>
      <div class="cat__face__pattern cat__face__pattern--left">
      </div>
      <div class="cat__face__pattern cat__face__pattern--right">
      </div>
      <div class="cat__eyes">
        <div class="cat__eyes__eye cat__eyes__eye--left">
          <div class="cat__eyes__eyelid cat__eyes__eyelid--left"></div>
          <span></span>
        </div>
        <div class="cat__eyes__eye cat__eyes__eye--right">
          <div class="cat__eyes__eyelid cat__eyes__eyelid--right"></div>
          <span></span>
        </div>
      </div>
      <div class="cat__nose">
      </div>
      <div class="cat__mouth">
      </div>
    </div>
    <div class="cat__hands">
      <div class="cat__hands__hand cat__hands__hand--left">
      </div>
      <div class="cat__hands__hand cat__hands__hand--right">
      </div>
    </div>

    <div class="cat__legs">
      <div class="cat__legs__leg cat__legs__leg--left">
      </div>
      <div class="cat__legs__leg cat__legs__leg--right">
      </div>
    </div>
    <div class="cat__tail">
      <div class="cat__tail__inner">
        <span class="cat__tail__pattern cat__tail__pattern--1"></span>
        <span class="cat__tail__pattern cat__tail__pattern--2"></span>
        <span class="cat__tail__pattern cat__tail__pattern--3"></span>
        <span class="cat__tail__pattern cat__tail__pattern--4"></span>
        <span class="cat__tail__pattern cat__tail__pattern--5"></span>
        <span class="cat__tail__pattern cat__tail__pattern--6"></span>
        <span class="cat__tail__pattern cat__tail__pattern--7"></span>
        <span class="cat__tail__pattern cat__tail__pattern--8"></span>
        <span class="cat__tail__pattern cat__tail__pattern--9"></span>
      </div>
    </div>
  </div>
</div>

.clock以下はアナログ時計の骨組みであり、文字盤span、中心軸ul.point、各針.needleで構成されています。

.cat以下は猫の構成を定義しています。

CSS(SCSS)でアナログ時計の見た目を作る

次に以下のCSSを書いてアナログ時計の見た目を作ります。

* {
  box-sizing: border-box;
}

html {
  font-size: 10vw;
  @media screen and (min-width: 500px) {
    font-size: 200%;
  }
}

body {
  background: #79D1B0;
  font-family: 'Vollkorn', serif;
  margin: 0;
  padding: 0;
}

ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.clock {
  -webkit-transform: translate(-50%, -50%);
  transform: translate(-50%, -50%);
  position: absolute;
  top: 50%;
  left: 50%;
  height: 6em;
  width: 6em;
  &__inner {
    background: #fff;
    border: 0.2em solid #F27398;
    border-radius: 50%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0%;
    width: 100%;
    z-index: 6;
  }
  &__nums {
    background: #F27398;
    border-radius: 100%;
    display: block;
    height: 0.5em;
    list-style: none;
    margin: 0;
    padding: 0;
    position: absolute;
    top: 50%;
    left: 50%;
    -webkit-transform: translate(-50%, -50%);
    transform: translate(-50%, -50%);
    width: 0.5em;
    z-index: 10;
  }
  &__num {
    height: 2.8em;
    position: absolute;
    left: 50%;
    bottom: 0.25em;
    margin-left: -0.25em;
    text-align: center;
    -webkit-transform-origin: center bottom;
    transform-origin: center bottom;
    width: 0.5em;
    z-index: 3;
    &--1 {
      -webkit-transform: rotate(30deg);
      transform: rotate(30deg);
    }
    &--2 {
      -webkit-transform: rotate(60deg);
      transform: rotate(60deg);
    }
    &--3 {
      -webkit-transform: rotate(90deg);
      transform: rotate(90deg);
    }
    &--4 {
      -webkit-transform: rotate(120deg);
      transform: rotate(120deg);
    }
    &--5 {
      -webkit-transform: rotate(150deg);
      transform: rotate(150deg);
    }
    &--6 {
      -webkit-transform: rotate(180deg);
      transform: rotate(180deg);
    }
    &--7 {
      -webkit-transform: rotate(210deg);
      transform: rotate(210deg);
    }
    &--8 {
      -webkit-transform: rotate(240deg);
      transform: rotate(240deg);
    }
    &--9 {
      -webkit-transform: rotate(270deg);
      transform: rotate(270deg);
    }
    &--10 {
      -webkit-transform: rotate(300deg);
      transform: rotate(300deg);
    }
    &--11 {
      -webkit-transform: rotate(330deg);
      transform: rotate(330deg);
    }
    &--12 {
      -webkit-transform: rotate(360deg);
      transform: rotate(360deg);
    }
    &__text {
      color: #95a5a6;
      display: inline-block;
      font-size: 0.8em;
      &--1 {
        -webkit-transform: rotate(-30deg);
        transform: rotate(-30deg);
      }
      &--2 {
        -webkit-transform: rotate(-60deg);
        transform: rotate(-60deg);
      }
      &--3 {
        -webkit-transform: rotate(-90deg);
        transform: rotate(-90deg);
      }
      &--4 {
        -webkit-transform: rotate(-120deg);
        transform: rotate(-120deg);
      }
      &--5 {
        -webkit-transform: rotate(-150deg);
        transform: rotate(-150deg);
      }
      &--6 {
        -webkit-transform: rotate(-180deg);
        transform: rotate(-180deg);
      }
      &--7 {
        -webkit-transform: rotate(-210deg);
        transform: rotate(-210deg);
      }
      &--8 {
        -webkit-transform: rotate(-240deg);
        transform: rotate(-240deg);
      }
      &--9 {
        -webkit-transform: rotate(-270deg);
        transform: rotate(-270deg);
      }
      &--10 {
        -webkit-transform: rotate(-300deg);
        transform: rotate(-300deg);
      }
      &--11 {
        -webkit-transform: rotate(-330deg);
        transform: rotate(-330deg);
      }
      &--12 {
        -webkit-transform: rotate(-360deg);
        transform: rotate(-360deg);
      }
    }
  }
  &__needles {
    height: 0.5em;
    position: absolute;
    top: 50%;
    left: 50%;
    -webkit-transform: translate(-50%, -50%);
    transform: translate(-50%, -50%);
    width: 0.5em;
    z-index: 9;
  }
  &__needle {
    border-radius: 0.08em 0.08em 0 0;
    height: 2em;
    margin-left: -0.08em;
    position: absolute;
    bottom: 0.25em;
    left: 50%;
    -webkit-transform-origin: center bottom;
    transform-origin: center bottom;
    width: 0.16em;
    &--hour {
      background: #95a5a6;
      height: 1.4em;
    }
    &--min {
      background: #F27398;
    }
    &--sec {
      background: #aaa;
      margin-left: -0.04em;
      width: 0.08em;
      &:before {
        background: #aaa;
        border-radius: 0 0 0.08em 0.08em;
        content: '';
        height: 0.8em;
        margin-left: -0.04em;
        position: absolute;
        top: 100%;
        left: 50%;
        width: 0.08em;
      }
    }
  }
}

.cat {
  height: 6em;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  &__face {
    background: #bda46b;
    border-radius: 50%;
    height: 4em;
    overflow: hidden;
    position: absolute;
    top: -3.5em;
    left: 0%;
    width: 6em;
    z-index: 5;
    &__pattern {
      background: #695b3b;
      position: absolute;
      &:before,
      &:after {
        background: #695b3b;
        content: '';
        position: absolute;
      }
      &--top {
        border-radius: 0 0 50% 50%;
        height: 1em;
        margin-left: -0.1em;
        top: 0;
        left: 50%;
        width: 0.2em;
        &:before {
          border-radius: 0 0 50% 50%;
          height: 0.6em;
          margin-left: -0.6em;
          top: 0;
          left: 50%;
          width: 0.2em;
        }
        &:after {
          border-radius: 0 0 50% 50%;
          height: 0.6em;
          margin-right: -0.6em;
          top: 0;
          right: 50%;
          width: 0.2em;
        }
      }
      &--left {
        border-radius: 0 50% 50% 0;
        height: 0.2em;
        margin-top: -0.1em;
        top: 50%;
        left: 0;
        width: 1em;
        &:before {
          border-radius: 0 50% 50% 0;
          height: 0.2em;
          margin-top: -0.6em;
          top: 50%;
          left: 0;
          width: 0.6em;
        }
        &:after {
          border-radius: 0 50% 50% 0;
          height: 0.2em;
          margin-bottom: -0.6em;
          bottom: 50%;
          left: 0;
          width: 0.6em;
        }
      }
      &--right {
        border-radius: 50% 0 0 50%;
        height: 0.2em;
        margin-top: -0.1em;
        top: 50%;
        right: 0;
        width: 1em;
        &:before {
          border-radius: 50% 0 0 50%;
          height: 0.2em;
          margin-top: -0.6em;
          top: 50%;
          right: 0;
          width: 0.6em;
        }
        &:after {
          border-radius: 50% 0 0 50%;
          height: 0.2em;
          margin-bottom: -0.6em;
          bottom: 50%;
          right: 0;
          width: 0.6em;
        }
      }
    }
  }
  &__ears {
    height: 1em;
    position: absolute;
    top: -3.5em;
    left: 0;
    width: 100%;
    z-index: -1;
    &__ear {
      background: #bda46b;
      border-radius: 25%;
      height: 1.8em;
      position: absolute;
      top: -0.3em;
      width: 1.8em;
      &:before {
        content: '';
        background: #f4e8da;
        border-radius: 25%;
        height: 1.4em;
        margin-top: -0.7em;
        margin-left: -0.7em;
        position: absolute;
        top: 50%;
        left: 50%;
        width: 1.4em;
      }
      &--left {
        left: 0.5em;
        transform: rotate(20deg);
      }
      &--right {
        right: 0.5em;
        transform: rotate(-20deg);
      }
    }
  }
  &__eyes {
    height: 1em;
    position: absolute;
    top: 1em;
    width: 100%;
    &__eye {
      background: #333;
      border: 0.06em solid #333;
      border-top: 0.12em solid #333;
      height: 1em;
      overflow: hidden;
      position: absolute;
      bottom: 0;
      width: 1.3em;
      &:before {
        background: #69CC68;
        content: '';
        height: 1em;
        position: absolute;
        top: 0;
        width: 1.2em;
      }
      &:after {
        background: #333;
        border-radius: 50%;
        content: '';
        height: 1em;
        margin-top: -0.25em;
        position: absolute;
        top: 0em;
        width: 1em;
      }
      span {
        background: #fff;
        border-radius: 50%;
        height: 0.2em;
        margin-left: -0.1em;
        margin-top: -0.2em;
        position: absolute;
        top: 50%;
        left: 50%;
        width: 0.2em;
        z-index: 1;
      }
      &--left {
        border-radius: 0 70% 0 70%;
        left: 1.1em;
        &:before {
          border-radius: 0 50% 50% 50%;
          left: 0;
        }
        &:after {
          margin-left: -0.45em;
          left: 50%;
        }
      }
      &--right {
        border-radius: 70% 0 70% 0;
        right: 1.1em;
        &:before {
          border-radius: 50% 0 50% 50%;
          right: 0;
        }
        &:after {
          margin-right: -0.45em;
          right: 50%;
        }
      }
    }
    &__eyelid {
      background: #bda46b;
      height: 0;
      position: absolute;
      left: 0;
      top: 0;
      width: 100%;
      z-index: 10;
    }
  }
  &__nose {
    border-top: 0.5em solid #F26752;
    border-left: 0.5em solid transparent;
    border-right: 0.5em solid transparent;
    height: 0.5em;
    margin-left: -0.5em;
    position: absolute;
    top: 2.5em;
    left: 50%;
    width: 1em;
  }
  &__mouth {
    margin-left: -1em;
    overflow: hidden;
    position: absolute;
    top: 3em;
    left: 50%;
    height: 1em;
    width: 2em;
    &:before,
    &:after {
      border: 0.01em solid #333;
      border-radius: 50%;
      content: '';
      display: block;
      height: 0.8em;
      position: absolute;
      top: -0.4em;
      width: 50%;
    }
    &:before {
      margin-left: -0.04em;
      left: 0;
    }
    &:after {
      margin-right: -0.04em;
      right: 0;
    }
  }
  &__whiskers {
    height: 1em;
    position: absolute;
    top: -1.5em;
    left: 0;
    width: 100%;
    z-index: 8;
    &__whisker {
      height: 1em;
      position: absolute;
      top: 0;
      width: 3em;
      &:before,
      &:after,
      span {
        border-top: 0.01em solid #eee;
        height: 0.01em;
        position: absolute;
        left: 0;
        width: 3em;
      }
      &--left {
        margin-left: -1em;
        left: 0;
        &:before {
          content: '';
          top: 0;
          -webkit-transform: rotate(10deg);
          transform: rotate(10deg);
        }
        &:after {
          content: '';
          margin-top: -0.005em;
          top: 0.5em;
        }
        span {
          bottom: 0;
          -webkit-transform: rotate(-10deg);
          transform: rotate(-10deg);
        }
      }
      &--right {
        margin-right: -1em;
        right: 0;
        &:before {
          content: '';
          top: 0;
          -webkit-transform: rotate(-10deg);
          transform: rotate(-10deg);
        }
        &:after {
          content: '';
          margin-top: -0.005em;
          top: 0.5em;
        }
        span {
          bottom: 0;
          -webkit-transform: rotate(10deg);
          transform: rotate(10deg);
        }
      }
    }
  }
  &__hands {
    height: 1em;
    position: absolute;
    top: -0.5em;
    left: 0;
    width: 100%;
    z-index: 9;
    &__hand {
      background: #a8925f;
      border-radius: 50%;
      height: 1em;
      overflow: hidden;
      position: absolute;
      top: 0.5em;
      width: 2em;
      &:before {
        border-right: 0.08em solid #333;
        content: '';
        position: absolute;
        bottom: 0;
        left: 0;
        height: 0.3em;
        width: 0.6em;
      }
      &:after {
        border-left: 0.08em solid #333;
        content: '';
        position: absolute;
        bottom: 0;
        right: 0;
        height: 0.3em;
        width: 0.6em;
      }
      &--left {
        left: 0;
        -webkit-transform: rotate(-20deg);
        transform: rotate(-20deg);
      }
      &--right {
        right: 0;
        -webkit-transform: rotate(20deg);
        transform: rotate(20deg);
      }
    }
  }
  &__legs {
    height: 1em;
    position: absolute;
    bottom: 0em;
    left: 0;
    width: 100%;
    z-index: 8;
    &:after {
      background: #bda46b;
      border-radius: 100%;
      content: '';
      height: 0.5em;
      margin-bottom: -0.25em;
      position: absolute;
      bottom: 0em;
      right: 0;
      width: 0.5em;
      z-index: 2;
    }
    &__leg {
      background: #a8925f;
      border-radius: 50%;
      height: 1em;
      overflow: hidden;
      position: absolute;
      top: 0;
      width: 2em;
      &:before {
        border-right: 0.08em solid #333;
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        height: 0.3em;
        width: 0.6em;
      }
      &:after {
        border-left: 0.08em solid #333;
        content: '';
        position: absolute;
        top: 0;
        right: 0;
        height: 0.3em;
        width: 0.6em;
      }
      &--left {
        left: 0;
        -webkit-transform: rotate(20deg);
        transform: rotate(20deg);
      }
      &--right {
        right: 0;
        -webkit-transform: rotate(-20deg);
        transform: rotate(-20deg);
      }
    }
  }
  &__tail {
    height: 2em;
    position: absolute;
    bottom: 0.04em;
    left: 0;
    width: 100%;
    z-index: 0;
    &__inner {
      background: #bda46b;
      border-radius: 0 0 3em 3em;
      height: 2em;
      overflow: hidden;
      position: absolute;
      top: 2em;
      left: 3em;
      width: 3em;
      &:before {
        background: #79D1B0;
        border-radius: 0 0 2em 2em;
        content: '';
        height: 3em;
        margin-left: -1em;
        overflow:hidden;
        position: absolute;
        top: -1.5em;
        left: 50%;
        width: 2em;
        z-index: 4;
      }
    }
    &__pattern {
      background: #695b3b;
      height: 0.2em;
      position: absolute;
      width: 0.5em;
      &:nth-child(odd) {
        border-radius: 50% 0 0 50%;
      }
      &:nth-child(even) {
        border-radius: 0 50% 50% 0;
      }
      &--1 {
        top: 0.2em;
        left: 0.1em;
      }
      &--2 {
        top: 0.7em;
        left: -0.1em;
        -webkit-transform: rotate(-30deg);
        transform: rotate(-30deg);
      }
      &--3 {
        top: 1em;
        left: 0.3em;
        -webkit-transform: rotate(-50deg);
        transform: rotate(-50deg);
      }
      &--4 {
        top: 1.5em;
        left: 0.5em;
        -webkit-transform: rotate(-60deg);
        transform: rotate(-60deg);
      }
      &--5 {
        top: 1.5em;
        left: 1.1em;
        -webkit-transform: rotate(-80deg);
        transform: rotate(-80deg);
      }
      &--6 {
        top: 1.7em;
        left: 1.6em;
        -webkit-transform: rotate(-100deg);
        transform: rotate(-100deg);
      }
      &--7 {
        top: 1.2em;
        left: 2em;
        -webkit-transform: rotate(-130deg);
        transform: rotate(-130deg);
      }
      &--8 {
        top: 0.9em;
        left: 2.6em;
        -webkit-transform: rotate(-150deg);
        transform: rotate(-150deg);
      }
      &--9 {
        top: 0.4em;
        left: 2.4em;
        -webkit-transform: rotate(-180deg);
        transform: rotate(-180deg);
        z-index: 3;
      }
    }
  }
}

@-webkit-keyframes blink {
  0% {
    height: 0;
  }
  50% {
    height: 100%;
  }
  100% {
    height: 0;
  }
}

@keyframes blink {
  0% {
    height: 0;
  }
  50% {
    height: 100%;
  }
  100% {
    height: 0;
  }
}

.blink {
  -webkit-animation: blink infinite 1s;
  animation: blink infinite 1s;
}

CSSのポイントは以下の通りです。

文字盤の配置方法

文字盤はliを利用して以下の手順を経て配置されます。

1. liの下端を回転軸にする
litransform-origin: center bottom;を指定し、liの下端を回転軸とします。

2. liを中心軸を基準に絶対配置
liを中心軸ulを基準に絶対配置します。

3. liを30度間隔で回転させる
transformrotateliを30度間隔で回転させて、数字spanが円周に沿うようにします。

rotate

4. 数字の傾きを直す
rotateliを回転させると中身のspanも一緒に回転されるため、数字が傾いてしまいます。

なのでspanrotateliとは逆の方向へ回転させて数字の傾きを直します。

猫を描く

猫は画像ではなくCSSでborder-radius等を駆使して描きます。

瞬きのアニメーションを作成

瞬きのアニメーションはCSSで作成します。始めに@keyframesで各キーフレームごと(今回は0%, 50%, 100%)に変化させる要素(今回は瞼.cat__eyes__eyelid)のプロパティ(今回はheight)を指定してキーフレームスを作成します。また、キーフレームスには名前(今回はblink)を付けます。

次に、.blinkクラスにanimationプロパティを記述してその値に先ほどのキーフレームスblinkとアニメーションの回数(今回は永遠に繰り返すinfinite)、アニメーションの時間を指定します。

この.blinkクラスがJavaScriptによって.cat__eyes__eyelidに追加されると猫が瞬きを開始します。

JavaScript(TypeScript)でアナログ時計を機能させる

最後に以下のJavaScriptを書いてアナログ時計を機能させます。

(() => {
  class Cat {
    private _eyelids: NodeList;

    constructor() {

      //瞼
      this._eyelids = document.querySelectorAll('.cat__eyes__eyelid');
    }

    get eyelids(): NodeList {
      return this._eyelids;
    }

    //猫を瞬きさせるメソッド
    blink() {

      //.cat__eyes__eyelidに.blinkクラスを追加
      this._eyelids.forEach((eyelid: HTMLElement) => eyelid.classList.add('blink'));
    }
  }

  class Needles {
    private needle;

    constructor() {

      //3つの針
      this.needle = {
        sec: document.getElementById('sec'),
        min: document.getElementById('min'),
        hour: document.getElementById('hour')
      }
    }

    //3つの針を回転させるメソッド
    rotate(deg): void {
      this.needle.sec.style.transform = `rotateZ(${deg.sec}deg)`;
      this.needle.min.style.transform = `rotateZ(${deg.min}deg)`;
      this.needle.hour.style.transform = `rotateZ(${deg.hour}deg)`;     
    }
  }

  //それぞれの針の振れ幅
  const increment = {
    sec: (360 / 60),
    min: (360 / 60),
    hour: (360 / 12)
  }

  class Clock {
    private needles: Needles;
    private cat: Cat;

    constructor() {
      this.needles = new Needles;
      this.cat = new Cat;

      //Clockクラスがインスタンス化されるとrunメソッドを実行
      this.run();
    }

    //時計を動かすメソッド
    run(): void {

      //現在の秒・分・時間を取得
      const date: Date = new Date();
      const sec: number = date.getSeconds();
      const min: number = date.getMinutes();
      const hour: number = date.getHours();

      //針の角度
      const deg = {
        sec: sec * increment.sec,
        min: min * increment.min,
        hour: hour * increment.hour + min * (360 / 12 / 60)
      }

      //針を回転させる
      this.needles.rotate(deg);

      //runメソッドを繰り返し実行
      requestAnimationFrame(this.run.bind(this));
    }
  }

  //時計を起動
  const clock: Clock = new Clock;
})();

JavaScriptでは以下のことを行っています。

Catクラスを作成

猫に関するCatクラスを作成します。猫といってもこのクラスは瞼eyelidsと瞬きさせるblinkメソッドのみを持ちます。

Needlesクラスを作成

時計の針に関するNeedlesクラスを作成します。このクラスは秒針sec・分針min・時針hourを持ったオブジェクトneedleと3つの針を回転させるrotateメソッドを持ちます。

針の回転は受け取った角度degrotateZに指定することで行います。

incrementオブジェクトを作成

それぞれの針の振れ幅を持つincrementオブジェクトを作成します。

秒針と分針の振れ幅は1周(360°)を60等分したうちの1つ分なので、360 / 60で6となります。時針の振れ幅は1周(360°)を12等分したうちの1つ分なので、360 / 12で30となります。

Clockクラスを作成

時計全体に関するClockクラスを作成します。このクラスはneedlecatオブジェクトに加え、時計を動かすrunメソッドを持ちます。

runメソッドでは現在の秒sec・分min・時間hourを取得してそれらに振れ幅incrementを掛けることで、1秒・1分間・1時間に進む針の角度degを求めています。

ですがここで一つ注意が必要です。1時間に進む針の角度をそのままhour * increment.hourとしてしまうと、1時間経過直後に針が30°進むため不自然な動きになってしまいます。

これを防ぐために1分経過毎に時針を少しずつ進めて(分刻み)いき、60分が経過すると時針が30°進んでいるようにします。これを実現するには以下の点を押さえて式を作ります。

押さえる点
1. 時針は1時間(60分)で30°(360 / 12)進む。
2. 1分では0.5°(30 / 60)進む。
3. 時針を1分で0.5°進むようにするにはhour * degmin * 0.5を足す。

完成した式

hour * deg + min * (360 / 12) / 60

式に当てはめてみる

//min: 33, hour: 16の場合
16 * 30 = 480deg

//1. 現在の時針の角度
480deg + 33 * (30 / 60) = 496.5deg

//2. 1.から1分後の角度
480deg + 34 * (30 / 60) = 497deg

//3. 2.から20分後の角度
480deg + 54 * (30 / 60) = 507deg(497+0.5 * 20)

//4. 3.から5分後の角度
480deg + 59 * (30 / 60) = 509.5deg(507 + 0.5 * 5)

//5. 4.から1分後(1時間経過)の角度
510deg(17 * 30) + 0(60) * (30 / 60) = 510deg 

これで時針が正常に動作します。

次に、前述で取得した角度をneedlesオブジェクトが持つrotateメソッドに渡して針を回転させています。

最後にrequestAnimationFrameメソッドにrunメソッドを渡すことでrunメソッドを繰り返し実行させています。

時計を起動する

最後にClockクラスをインスタンス化すると時計が動き出します。

さいごに

以上で、HTML+CSS+JavaScriptで猫のアナログ時計を作る方法を終わります。

参考文献