LoginSignup
14
11

More than 5 years have passed since last update.

setIntervalより正確な時計を作る

Posted at

setIntervalは手軽ですが、それが故に正確な時間を必要とする場合には、あまり役に立たなくなります。

手軽に時計を作ってみる

setInterval(関数, 1000);のように書けば、関数を1秒毎に実行することとなります。ここで描画処理を入れれば、秒単位で表示する時計の一丁上がりです。

…が、このままではうまくいかない箇所があります。

  • スタートのタイミングの問題…setIntervalをスタートする瞬間がちょうど秒の変わる瞬間だったという幸運なパターンもありえますが、最悪の場合1秒近く遅れることとなります。
  • 1秒より間隔が長くなる…setIntervalの時間は、「前の実行が終わってから次の実行が始まるまでに、最低限確保される待ち時間」ですので、実行同士の間隔は関数自体の実行時間が足し込まれて、必ずセットした時間を上回ることになります。また、他のコードの実行で遅れることもありえますし、ブラウザ側で意図的に(消費電力節約のためなどで)それ以上待たせてもいいことになっています(W3C HTML 5.2)。
    • 上の2つが重なれば、「遅れがそこまででもないのに秒が飛ぶ」ことも考えられます。1回めを0.999秒で実行開始したとして、タイマーが2ミリ秒余分にかかれば、次の実行は2.001秒となって、1秒台の実行がなくなってしまいます1

このような事情がありますので、「できるだけ安定したサイクルを保ちたい」あるいは「秒が変わった瞬間に実行したい」という場合、setIntervalはあまり適当な選択肢ではありません。

setTimeoutを使う

では、代わりにsetTimeoutを使ってみましょう。setTimeoutでループ処理をさせるときの基本形は、以下のような形です。タイマーで呼ばれた関数から、さらに同じ関数をsetTimeoutする、という流れです。

setTimeout(function main(){
  // メインの処理

  setTimeout(main, 時間);
}, 時間);

あとは時間をどう設定するかだけですが、Date.now()で現在のミリ秒が得られます。「ちょうど秒が変わる瞬間」はシリアル値が1000で割り切れますので、1000 - Date.now() % 1000のようにすれば、そこまでの待ち時間を導けます。

毎回待ち時間を正しく設定することで、秒の変わり目にできるだけ近いところでコンスタントに関数を実行できますし、処理の遅延が1秒以上にならない限り、秒が飛ぶことも起きません。

Codepen上に作ってみました

リロードなどでタイミングを調節すれば、setIntervalのほうが遅れている様子もわかるかと思います。

(なぜかReactで作ってしまったので、ちょっと見づらいかもしれません)

See the Pen pYGKgR by Jkr2255 (@jkr2255) on CodePen.


  1. もちろん、ブラウザ処理の重さなどで、実行が遅れうることは予め想定して実装が必要なのは間違いないです。 

14
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
11