ReactでHIIT用のインターバルタイマーアプリを作ってみた【作成方法・サンプルコード・解説あり】

2021-05-07
Main Image

目次

こんにちは。

今回は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}

サークルの描画部分。progressColorbgColorprogressはワークアウト中か休憩中かによって分ける必要があるので、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なども出てきて盛りだくさんでした。

ひとつ簡単なアプリを作るだけでもとても勉強になりました。

そのうちより使いやすくバージョンアップしていければと思います。

ads【オススメ】未経験からプログラマーへ転職できる【GEEK JOBキャンプ】
▼ Amazonオススメ商品
ディスプレイライト デスクライト BenQ ScreenBar モニター掛け式
スマートLEDフロアライト 間接照明 Alexa/Google Home対応

Author

Penta

都内で働くITエンジニアもどき。好きなものは音楽・健康・貯金・シンプルでミニマルな暮らし。AWSクラウドやデータサイエンスを勉強中。学んだことや体験談をのんびり書いてます。TypeScript / Next.js / React / Python / AWS / インデックス投資 / 高配当株投資 More profile

Location : Tokyo, JPN

Contact : Twitter@penguinchord

Recommended Posts

Copy Right / Penguin Chord, ペンギンコード (penguinchord.com) 2022 / Twitter@penguinchord