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の下端を回転軸にする
li
にtransform-origin: center bottom;
を指定し、li
の下端を回転軸とします。
2. liを中心軸を基準に絶対配置
li
を中心軸ul
を基準に絶対配置します。
3. liを30度間隔で回転させる
transform
のrotate
でli
を30度間隔で回転させて、数字span
が円周に沿うようにします。
4. 数字の傾きを直す
rotate
でli
を回転させると中身のspan
も一緒に回転されるため、数字が傾いてしまいます。
なのでspan
をrotate
でli
とは逆の方向へ回転させて数字の傾きを直します。
猫を描く
猫は画像ではなく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
メソッドを持ちます。
針の回転は受け取った角度deg
をrotateZ
に指定することで行います。
incrementオブジェクトを作成
それぞれの針の振れ幅を持つincrement
オブジェクトを作成します。
秒針と分針の振れ幅は1周(360°)を60等分したうちの1つ分なので、360 / 60で6となります。時針の振れ幅は1周(360°)を12等分したうちの1つ分なので、360 / 12で30となります。
Clockクラスを作成
時計全体に関するClock
クラスを作成します。このクラスはneedle
とcat
オブジェクトに加え、時計を動かす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 * deg
にmin * 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で猫のアナログ時計を作る方法を終わります。