setIntervalやsetTimeoutを使った処理中に別タブを開くと、元のタブに戻ってきた際に挙動がおかしくなることがあるのは把握していたのですが、具体的にどういった挙動なのかとその対策について考えてみます。
サンプルコード
setInterval()で100ミリ秒毎にカウントするタイマーを実装してみます。
<div class="timer"></div>
.timerとconsoleにタイマーを表示するようにします。
let count = 0; setInterval(function() { count++; const timer = count / 10; document.querySelector('.timer').innerHTML = timer; console.log(timer); }, 100);
これでsetInterval()を使ったタイマーが実装できました。
setInterval()を使ったタイマーのデモページ
デモページを開いて別タブに切り替えた後にconsoleを確認してみたところ、ざっくりと目視で確認してみた限り以下のような挙動になっているようでした。
- chromeとedgeの場合、別タブを開いた際にはsetInterval()の実行が100ミリ秒毎から1秒毎変更され、そこから60秒後に1分毎の実行に変更される。
- Firefoxの場合、別タブを開いた際にはsetInterval()の実行が100ミリ秒毎から1秒毎変更され、以降は1秒毎に実行され続ける。
- Safariの場合は挙動のルールがよくわからず、別タブを開いた後の数秒間は実行が100ミリ秒毎から1秒毎変更されるようなのですが、以降は間隔が長くなったり短くなったりでバラつきがありつつ、徐々に間隔は長くなっていく傾向にあるようでした。
次にsetTimeout()を使ったパターンを試してみます。
処理内容としては先ほどのデモと同じです。
let count = 0; timer_func(); function timer_func() { count++; const timer = count / 10; document.querySelector('.timer').innerHTML = timer; console.log(timer); setTimeout(timer_func, 100); }
setTimeout()を使ったタイマーのデモページ
結果としてはsetInterval()の時と同様でした。
次にこの挙動の対策ですが、今回のように単純にカウントしていくのみ(別タブから戻ってきたときにタイマーの値が合っていればよい)であれば、カウント開始時のミリ秒を取得しておき、カウント毎にミリ秒の差を使ってカウントすると対応できそうです。
const startTime = Date.now(); setInterval(function() { const currentTime = Date.now(); const timer = (currentTime - startTime) / 1000; document.querySelector('.timer').innerHTML = timer; console.log(timer); }, 100);
Date.now()を使った対策のデモページ
これで実行回数が減っていてもカウントはズレないようになりました。
setTimeout()の場合も同様に対応できます。
const startTime = Date.now(); timer_func(); function timer_func() { const currentTime = Date.now(); const timer = (currentTime - startTime) / 1000; document.querySelector('.timer').innerHTML = timer; console.log(timer); setTimeout(timer_func, 100); }
簡易的な対応であれば上記方法でよいのですが、処理内容によってはこの方法で対策できない場合もあります。
その場合の別の方法として、Web Workerを使う方法もあります。
Web Workerについては過去に記事を投稿していますので、詳しくはそちらをご確認ください。
まずは呼び出し元の処理です。
const worker = new Worker('worker.js'); worker.onmessage = function(e) { const timer = e.data; document.querySelector('.timer').innerHTML = timer; console.log(timer); }
worker.jsというファイルを呼び出すようにしています。
次にそのworker.js内でsetInterval()の処理を行います。
let count = 0; setInterval(function() { count++; const timer = count / 10; postMessage(timer); }, 100);
Web Workerを使った対策のデモページ
これで別タブを開いても処理回数が減らなくなりました。
次にsetTimeout()の場合のworker.jsです。
let count = 0; timer_func(); function timer_func() { count++; const timer = count / 10; postMessage(timer); setTimeout(timer_func, 100); }
コメントが承認されるまで時間がかかります。