ReactでHIIT用のインターバルタイマーアプリを作ってみた【作成方法・サンプルコード・解説あり】
目次
こんにちは。
今回はReactを使ってsHIIT用のインターバルタイマーを作ってみました。
完成形はこちらです。
webブラウザでスマホからもPCからもダウンロード不要で使えます。ぜひHIITの際はご利用ください。
改善点があればTwitter@penguinchordで教えてね!
本記事では作り方を簡単にご紹介します。ご参考に。
状態遷移のロジック
以下のように定義しました。
isWait
: タイマーを動作させていない待機状態。タイマースタート前、ストップ後にTrue
になる。isWork
:True
ならワークアウト中のカウント、False
なら休憩(インターバル)中のカウント。
状態の管理はReact HooksのuseState
を利用します。
const [isWait, setIsWait] = useState<boolean>(true)
const [isWork, setIsWork] = useState<boolean>(false)
状態遷移は以下の関数で行います。このアプリの本体です。各パラメータの説明は後述。
const stateSwitcher = () =>{
isWait?(
count(timer,0)
):(
isWork?(
count(timer,workTime),
loopCount==loopLimit&&wait()
):(
count(timer,restTime)
)
)
}
入力パラメータ
ワークアウト時間、休憩時間、リピートするセット数、現在のセット数を管理します。
const [workTime, setWorkTime] = useState<number>(workTimeDefault)
const [restTime, setRestTime] = useState<number>(restTimeDefault)
const [loopLimit, setLoopLimit] = useState<number>(loopLimitDefault)
現在のセット数もuseState
で管理します。超便利。
const [loopCount, setLoopCount] = useState<number>(0)
タイマー処理
タイマーはsetInterval()
を用いて以下のようなcount
関数を実装しました。
0.25秒ずつカウントします。
タイマーの時間を管理する変数。
const [timer, setTimer] = useState<number>(0);
カウントする関数。スタートボタンを押したら呼び出します。
const count = (time : number, limit:number) => {
useEffect(()=>{
if (time<limit && !isWait){
const interval = setInterval(() => {
setTimer(time + .25)
}, 250);
return () => clearInterval(interval);
}else if(time==limit && !isWait){
setTimer(0)
setLoopCount(isWork?loopCount:loopCount+1)
setIsWork(!isWork)
}else{
setTimer(0)
}
},[time, limit])
}
上記関数内で、limit時間(ワークアウト中はworkTime
、休憩中はrestTime
)になったらセット数(loopCount
)を+1
する処理も行っています。
また、useEffectの第二引数に[time, limit]
を設定することで、これらの変数の値が更新されない限り、この関数が再レンダーの影響を受けないようにしています。この第二引数がないと、コード内の他の部分のレンダーにつられてタイマーが更新されてしまうので、意図していたインターバルでタイマーが増えていかないというバグ(1秒に1ではなく2増える等)になります。
タイマーのプログレスバー(円)
ベースはreact-circle
(
https://github.com/zzarcon/react-circle
)を参考にしました。
というか、ほぼcircle.tsx
の中身のコピペです。
円の中央のテキストが%表示だったので、タイマー表示に合うように以下のnow
というprops
を渡して、return <text>...</text>
の中で{progress}
の代わりに切り捨て秒数を表示させるようにしました。
now={Math.ceil((isWait?workTime:(isWork?workTime:restTime)-timer)) as unknown as string}
サークルの描画部分。progressColor
、bgColor
、progress
はワークアウト中か休憩中かによって分ける必要があるので、3項演算子((条件式)?(Trueの返り値):(Falseの返り値)
)で場合分けしています。
<Circle
animate={!isWait&&timer>0}
size='300'
progressColor={isWork?'rgb(76, 154, 255)':'#ecedf0'}
bgColor={isWork?'#ecedf0':'rgb(76, 154, 255)'}
now={Math.ceil((isWait?workTime:(isWork?workTime:restTime)-timer)) as unknown as string}
progress={100*timer/(isWork?workTime:restTime)}
showPercentageSymbol={false}
/>
JavaScriptでサウンド(正弦波)を鳴らす Web Audio API
Web Audio API
を利用しました。
理由はMP3などの音声ファイルを使わずにJavaScriptだけで正弦波を再生できるためです。
初期設定部分。AudioContext
オブジェクトは1度だけ生成すれば良いのでuseEffect
の第二引数に空配列[]
を渡します。
const [audioCtx,setAudioCtx] = useState<AudioContext>(undefined)
const [oscillator,setOscillator] = useState(undefined)
useEffect(()=>{
setAudioCtx(new AudioContext)
},[])
再生を行う関数。ここで周波数もセットしています。
const play = (hertz:number) => (
oscillator.frequency.value = hertz,
oscillator.connect(audioCtx.destination),
oscillator.start()
)
停止を行う関数。このアプリ内では0.25秒だけ鳴らすので、play()
を呼び出した後、timer
の値が0.25増えたらstop()
を呼ぶ、という処理にしています。
const stop = () => oscillator.stop()
Web Audio APIのドキュメント : Web Audio API(https://webaudio.github.io/web-audio-api/)
React Hooksでタイマーを作る
副作用フックの利用法(https://ja.reactjs.org/docs/hooks-effect.html)
JavaScriptで等差数列の配列を作成
ドロップダウンでワークアウトや休憩の秒数を5秒間隔で選択させるため、等差数列のリストを作ります。
等差数列を作るにはArray.from()
を使用します。
参考: MDN Web Docs - Array.from()
ドロップダウンを出力する関数。step
引数に秒数の間隔を渡します。また、map関数で配列の中身を順番に<Dropdown.Item>
化しています。
const echoDropdown = (step:number,changeTime:SelectCallback) => {
const sList = Array.from({length:12}, (v,i) => ((i+1)*step))
return(
sList.map((s)=>(
<Dropdown.Item
as="button"
onSelect={changeTime}
eventKey={s as unknown as string}>
{s}
</Dropdown.Item>
)
)
)
}
上記関数の引数changeTime
に渡す関数。
const changeWorkTime = (e) => setWorkTime(e as number)
const changeRestTime = (e) => setRestTime(e as number)
const changeLoopLimit = (e) => setLoopLimit(e as number)
ドロップダウンで選択(onSelect
)した値e
を渡してuseState
のset関数のパラメータとします。これで選択した値がワークアウト時間や休憩時間、あとセット回数に反映されるようになりました。
ドロップダウンボタンやアイテムはReact Bootstrap
のデザインを利用しています。
Dropdowns (https://react-bootstrap.netlify.app/components/dropdowns/)
まとめ
というわけで、今回はHIITに使うインターバルタイマーをReactで作ってみました。
React Hooks、Web Audio API、React Bootstrapなども出てきて盛りだくさんでした。
ひとつ簡単なアプリを作るだけでもとても勉強になりました。
そのうちより使いやすくバージョンアップしていければと思います。