Vueで2つ目に作ったものがなぜかアナログ時計だったお話


- [Vueで最初に作ったものがなぜかデジタル時計だったお話](https://qiita.com/b1san/items/29a0d9b30f7ea72ac028)

こちらの続きです。

デジタル時計作ったならやっぱりアナログ時計も作っておかないと…。

というわけで完成品がコチラ。

今回はアニメーションは使っておりません。
各針は角度をミリ秒単位で計算して動かしています。

![analog_clock.gif](https://qiita-image-store.s3.amazonaws.com/0/307269/61586725-ca4e-f2dc-7be1-d654ff815b10.gif)

# 構成図

![analog_clock_pats_discription.PNG](https://qiita-image-store.s3.amazonaws.com/0/307269/3282bd4f-c49d-b4a8-4913-fda56de44b10.png)

針（Hand）と文字盤（Dials)の2つでアナログ時計（AnalogClock）を構成していきます。

非常にシンプルですね。

# Hand

```vue:Hand.vue
<template>
    <div class="hand" :class="type" :style="{'transform': 'rotate(' + rotate + 'deg)'}" />
</template>

<script>
export default {
    props: {
        rotate: 0,
        type: ''
    },
}
</script>

<style scoped>
.hand {
    background-color: rgba(255,255,255,0.8);
}

.seconds {
    width: 1px;
    height: 150px;
    transform-origin: 0px 130px;
}

.minutes {
    width: 3px;
    height :150px;
    transform-origin: 1.5px 130px;
}

.hours {
    width: 5px;
    height: 100px;
    transform-origin: 2.5px 80px;
}
</style>
```

typeで針の種類、秒針（seconds)、分針（minutes）、時針（hours）を指定します。

rotateに針の角度を指定し、styleバインディングで動的に回していきます。

# Dials

```vue:Dials.vue
<template>
    <div class="dials">
        <div 
            v-for="n in 60" 
            :key="n"
            :style="{
                'top':top(n) + 'px',
                'left': left(n) + 'px',
                'transform': 'rotate(' + rotate(n) + 'deg)'}"
            :class="['scale', {'fifth': n % 5 == 0}]">
            <p v-if="n % 5 == 0" 
               :style="{'transform': 'rotate(' + -rotate(n) + 'deg)'}">
                {{ n / 5 }}
            </p>
        </div>
    </div>
</template>

<script>
export default {
    methods: {
        top(val) {
            return 150 - Math.cos(Math.PI / 30 * val) * 150
        },

        left(val) {
            return 150 + Math.sin(Math.PI / 30 * val) * 150
        },

        rotate(val) {
            return 6 * val
        }
    }
}
</script>

<style scoped>
.dials {
    background-color: #333;
    border-radius: 300px;

    position: relative;
    
    height: 300px;
    width: 300px;
}

.scale {
    background-color: white;

    position: absolute;

    width: 1px;
    height: 10px;
    
    transform-origin: left top;
    
}

.fifth {
    width: 5px;
    height: 20px;
}

.fifth p {
    margin-top: 20px;
    margin-left: -15px;

    color: white;
    font-size: 28px;
    font-weight: bold;
    text-align: center;

    width: 35px;
}
</style>
```

60個の目盛をループで位置（left, top）と向き（rotate）を計算しながら設定しています。

位置に関しては以下のように高校数学で学んだことを活用します。（懐かしい…）

![location_circle.png](https://qiita-image-store.s3.amazonaws.com/0/307269/e2da8877-36cf-0472-3ffc-74650ffeaa27.png)

あとは5の倍数で目盛を太くして文字を表示しているだけです。

# AnalogClock

```vue:AnalogClock.vue
<template>
    <div class="clock">
        <dials />
        <hand class="hand seconds" type="seconds" :rotate="seconds"/>
        <hand class="hand minutes" type="minutes" :rotate="minutes" />
        <hand class="hand hours"   type="hours"   :rotate="hours"/>
    </div>
</template>

<script>
import Hand from './Hand'
import Dials from './Dials'
import moment from 'moment'

export default {
    components: {
        Hand,
        Dials
    },

    data() {
        return {
            intervalId: undefined,
            time: undefined
        }
    },

    computed: {
        seconds() {
            let ss = moment(this.time).seconds()
            let nn = moment(this.time).milliseconds()
            return 6 * (ss + nn / 1000) 
        },

        minutes() {
            let mm = moment(this.time).minutes()
            return 6 * (mm + this.seconds / 360)
        },

        hours() {
            let hh = moment(this.time).hours()
            return 30 * (hh + this.minutes / 360)
        }
    },

    methods: {
        setTime() {
            this.intervalId = setInterval(() => {
                this.time = new Date()
            }, 10)
        }
    },

    mounted() {
        this.setTime()
    },

    beforeDestroy() {
        clearInterval(this.intervalId)
    },
}
</script>

<style scoped>
.clock {
    position: relative;
}

.hand {
    position: absolute;
}

.seconds {
    left: 150px;
    top: 20px;
}

.minutes {
    left: 149px;
    top: 20px;
}

.hours {
    left: 148px;
    top: 70px;
}
</style>
```

時刻の取得については[デジタル時計編](https://qiita.com/b1san/items/29a0d9b30f7ea72ac028)を参照してください。

針の動きを滑らかにするためにミリ秒を含めて角度の計算をしています。

簡単に解説をすると、例えば秒針の場合、秒の値だけだと0°→6°→12°→… というように角度が飛んでしまいます。
それをミリ秒を含めることで補完しています。

transitionでも滑らかに動かせますが、360°になるときに考慮が必要になります。

# おわりに

さすがにもう時計シリーズはいいかな…。