というわけで、下記のように現在時刻をアナログ・デジタルの両方で表示する時計を作りました。特段、スゴイことを書いてあることではありませんのご承知おきください。
利用技術
- emotion/react 11.8.2
- emotion/styled 11.8.1
- react 17.0.2
- typescript 4.6.3
- dayjs 1.11.0
リンク先について
GitHubでソースを公開しています。issue/プルリク募集しています!
またデモについてはこちらで公開しています。
ソースコードについて
とくに特別なところもないですが、自分なりに気をつけたことを書いておきます。
ディレクトリ構成について
下記構成でディレクトリをつくりました。
最近、みた海外のYouTuberがやっていたので、真似てみました。
src/
├ businesses/
| └ buildHand.tsx
├ components/
| ├ AnalogClock.tsx
| ├ Clocks.tsx
| ├ DigitalClock.tsx
| └ Hand.tsx
├ hooks/
| └ useTime.tsx
├ interfaces/
| ├ ClockNum.tsx
| └ TimeFormat.tsx
├ App.tsx
└ index.tsx
- businesses:ビジネスロジックを記述するファイルをまとめたディレクトリ
- components:コンポーネントをまとめたディレクトリ
- hooks:カスタムフックをまとめたディレクトリ
- interfaces:インターフェイスをまとめたディレクトリ
Clocks.tsx
時間の状態を管理をするためのカスタムフックを作り、timeという定数にいれます。
timeはデジタル時計とアナログ時計の両方のコンポーネントで使うのでpropsとして渡します。
import DigitalClock from 'components/DigitalClock'
import AnalogClock from 'components/AnalogClock'
import { useTime } from 'hooks/useTime'
const Clocks: React.VFC = () => {
const time = useTime(1000)
return (
<>
<AnalogClock time={time} />
<DigitalClock time={time} />
</>
)
}
export default Clocks
useTime.tsx
毎秒、Date関数で取得した現在時刻を取得。stateを更新して返すようにしています。
import { useEffect, useState } from 'react'
export const useTime = (interval: number) => {
const [time, updateTime] = useState(Date.now())
useEffect(() => {
const timeoutId = setTimeout(() => updateTime(Date.now()), interval)
return () => {
clearTimeout(timeoutId)
}
}, [time]) // eslint-disable-line react-hooks/exhaustive-deps
return time
}
DigitalClock.tsx
propsとして現在時刻を受け取っているので、日付操作ライブラリのday.jsで整形して表示させます。
import dayjs from 'dayjs'
import { ClockNum } from 'interfaces/ClockNum'
import styled from '@emotion/styled'
const DigitalClock: React.VFC<ClockNum> = ({ time }) => {
return <SClockText>{dayjs(time).format('HH:mm:ss')}</SClockText>
}
const SClockText = styled.div`
font-size: 50px;
color: #ffffff;
margin-top: 100px;
`
export default DigitalClock
AnalogClock.tsx
アナログ時計ではpropsとして受け取った現在時刻から、時針・分針・秒針を生成する必要があります。
そのために、以下2つを準備しました。
buildHandメソッド
針の種類や角度の情報を含んだ配列を生成するメソッド
Handコンポーネント
buildHandメソッドから返された配列から実際に針を描画するコンポーネント
import styled from '@emotion/styled'
import { buildHand } from 'businesses/buildHand'
import { ClockNum } from 'interfaces/ClockNum'
import { TimeFormat } from 'interfaces/TimeFormat'
import { useEffect, useState } from 'react'
import Hand from './Hand'
const AnalogClock: React.VFC<ClockNum> = ({ time }) => {
const [hand, setHand] = useState<TimeFormat[]>([])
useEffect(() => {
setHand(buildHand())
}, [])
return (
<SClockBoard>
{hand.map((value, index) => (
<Hand key={index} time={time} format={value.format} separate={value.separate} />
))}
</SClockBoard>
)
}
const SClockBoard = styled.div`
width: 400px;
height: 400px;
border: 1px solid #ffffff;
border-radius: 100%;
position: relative;
&:before {
content: '';
diplay: block;
width: 5px;
height: 5px;
border-radius: 100%;
background-color: #ffffff;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
`
export default AnalogClock
buildHand.tsx
formatキーは針の種類です。
- h:時針
- m:分針
- s;秒針
separateキーは針の角度が何分割されるか表します。
1週は360度なので、それぞれ適切な分割数で割ります。
export const buildHand = () => {
const timeFormatArray = [
{
format: 'h',
separate: 12,
},
{
format: 'm',
separate: 60,
},
{
format: 's',
separate: 60,
},
]
return timeFormatArray.map((value) => {
return {
format: value.format,
separate: 360 / value.separate,
}
})
}
Hand.tsx
buildHandメソッドで生成された配列情報から針を描画します。
cssのtransformプロパティで角度を変えるようにしました。
import styled from '@emotion/styled'
import { ClockNum } from 'interfaces/ClockNum'
import { TimeFormat } from 'interfaces/TimeFormat'
import dayjs from 'dayjs'
type Props = ClockNum & TimeFormat
const Hand: React.VFC<Props> = ({ time, format, separate }) => {
const now: number = Number(dayjs(time).format(format))
const Angle = now * separate
let handStyles
if (format === 'h') {
handStyles = {
height: '130px',
top: '70px',
}
}
if (format === 'm') {
handStyles = {
height: '180px',
top: '20px',
}
}
if (format === 's') {
handStyles = {
height: '180px',
top: '20px',
width: '2px',
}
}
const SSecHand = styled.div`
width: 4px;
background-color: #ffffff;
border-radius: 100em;
position: absolute;
left: 50%;
transform-origin: bottom center;
transform: translateX(-50%) rotate(${Angle}deg);
${handStyles}
`
return <SSecHand />
}
export default Hand
まとめ
簡単な時計を作るだけでしたが、カスタムフックや繰り返し処理、コンポーネント分割の考慮など幅広く学習できたかと思います。