web-dev-qa-db-ja.com

Chromeでタブが非アクティブなときにsetIntervalを機能させるにはどうすればよいですか?

setIntervalを1秒間に30回実行しています。これはうまく機能しますが、別のタブを選択すると(コードのタブが非アクティブになるように)、何らかの理由でsetIntervalがアイドル状態に設定されます。

この単純なテストケースを作成しました( http://jsfiddle.net/7f6DX/3/ ):

var $div = $('div');
var a = 0;

setInterval(function() {
    a++;
    $div.css("left", a)
}, 1000 / 30);

このコードを実行してから別のタブに切り替えて、数秒待ってから戻ると、アニメーションは別のタブに切り替えたときの位置で継続します。そのため、タブが非アクティブの場合、アニメーションは1秒間に30回実行されません。これは、setInterval関数が毎秒呼び出される回数をカウントすることで確認できます。これは30ではなく、タブが非アクティブの場合は1または2になります。

これはパフォーマンスを改善するために設計によって行われていると思いますが、この動作を無効にする方法はありますか?実際、私のシナリオでは不利です。

169
pimvdb

ほとんどのブラウザでは、非アクティブなタブの実行の優先度は低く、これはJavaScriptタイマーに影響を与える可能性があります。

トランジションの値がフレーム間の経過時間を使用して計算された場合、代わりに各間隔の増分を固定した場合、この問題を回避するだけでなく、 requestAnimationFrame を使用してアニメーションを窒息させます。これは、プロセッサが非常にビジーでない場合に最大60fpsを取得できるためです。

以下は、requestAnimationFrameを使用したアニメーションプロパティ遷移のバニラJavaScriptの例です。

var target = document.querySelector('div#target')
var startedAt, duration = 3000
var domain = [-100, window.innerWidth]
var range = domain[1] - domain[0]

function start() {
  startedAt = Date.now()
  updateTarget(0)
  requestAnimationFrame(update)
}

function update() {
  let elapsedTime = Date.now() - startedAt

  // playback is a value between 0 and 1
  // being 0 the start of the animation and 1 its end
  let playback = elapsedTime / duration

  updateTarget(playback)
  
  if (playback > 0 && playback < 1) {
        // Queue the next frame
        requestAnimationFrame(update)
  } else {
        // Wait for a while and restart the animation
        setTimeout(start, duration/10)
  }
}

function updateTarget(playback) {
  // Uncomment the line below to reverse the animation
  // playback = 1 - playback

  // Update the target properties based on the playback position
  let position = domain[0] + (playback * range)
  target.style.left = position + 'px'
  target.style.top = position + 'px'
  target.style.transform = 'scale(' + playback * 3 + ')'
}

start()
body {
  overflow: hidden;
}

div {
    position: absolute;
    white-space: nowrap;
}
<div id="target">...HERE WE GO</div>

バックグラウンドタスクの場合(非UI関連)

@UpTheCreekコメント:

プレゼンテーションの問題には問題ありませんが、実行し続ける必要があることがいくつかあります。

特定の間隔で正確に実行する必要があるバックグラウンドタスクがある場合は、 HTML5 Web Workers を使用できます。詳細については、 Möhreの答え をご覧ください...

CSS vs JS「アニメーション」

この問題と 他の多くの は、JavaScriptベースのアニメーションの代わりにCSSトランジション/アニメーションを使用することで回避でき、かなりのオーバーヘッドが追加されます。これをお勧めします jQuery pluginanimate()メソッドと同様に、CSSトランジションを利用できます。

140
kbtzr

オーディオフェージングとHTML5プレーヤーで同じ問題に遭遇しました。タブが非アクティブになるとスタックしました。そのため、WebWorkerは制限なしで間隔/タイムアウトを使用できることがわかりました。メインのjavascriptに「チェック」を投稿するために使用します。

WebWorkersコード:

var fading = false;
var interval;
self.addEventListener('message', function(e){
    switch (e.data) {
        case 'start':
            if (!fading){
                fading = true;
                interval = setInterval(function(){
                    self.postMessage('tick');
                }, 50);
            }
            break;
        case 'stop':
            clearInterval(interval);
            fading = false;
            break;
    };
}, false);

メインJavascript:

var player = new Audio();
player.fader = new Worker('js/fader.js');
player.faderPosition = 0.0;
player.faderTargetVolume = 1.0;
player.faderCallback = function(){};
player.fadeTo = function(volume, func){
    console.log('fadeTo called');
    if (func) this.faderCallback = func;
    this.faderTargetVolume = volume;
    this.fader.postMessage('start');
}
player.fader.addEventListener('message', function(e){
    console.log('fader tick');
    if (player.faderTargetVolume > player.volume){
        player.faderPosition -= 0.02;
    } else {
        player.faderPosition += 0.02;
    }
    var newVolume = Math.pow(player.faderPosition - 1, 2);
    if (newVolume > 0.999){
        player.volume = newVolume = 1.0;
        player.fader.postMessage('stop');
        player.faderCallback();
    } else if (newVolume < 0.001) {
        player.volume = newVolume = 0.0;
        player.fader.postMessage('stop');
        player.faderCallback();
    } else {
        player.volume = newVolume;
    }
});
66
Möhre

Web Workersを使用するソリューションがあります(前述のとおり)。これらは別のプロセスで実行され、速度が低下しないためです

コードを変更せずに使用できる小さなスクリプトを作成しました。関数setTimeout、clearTimeout、setInterval、clearIntervalをオーバーライドするだけです。

すべてのコードの前にそれを含めてください。

詳細はこちら

35
Ruslan Tushov

これを行うだけです:

var $div = $('div');
var a = 0;

setInterval(function() {
    a++;
    $div.stop(true,true).css("left", a);
}, 1000 / 30);

非アクティブなブラウザタブは、setIntervalまたはsetTimeout関数の一部をバッファリングします。

stop(true,true)は、バッファされたすべてのイベントを停止し、最後のアニメーションのみを即座に実行します。

window.setTimeout()メソッドは、非アクティブなタブで1秒あたり1つしかタイムアウトしないようにクランプするようになりました。さらに、ネストされたタイムアウトを、HTML5仕様で許可されている最小値である4ミリ秒にクランプするようになりました(クランプに使用した10ミリ秒ではなく)。

13
www

この問題についての最良の理解はこの例にあると思います: http://jsfiddle.net/TAHDb/

私はここで簡単なことをしています:

1秒の間隔を空けて、毎回最初のスパンを非表示にして最後に移動し、2番目のスパンを表示します。

ページにとどまると、想定どおりに機能します。ただし、タブを数秒間非表示にすると、戻ったときに奇妙なものが表示されます。

非アクティブな時間中に発生しなかったすべてのイベントが1回ですべて発生するようになります。そのため、数秒間、Xイベントのようになります。非常に高速であるため、6つのスパンすべてを一度に表示できます。

したがって、chromeは継ぎ目がなく、イベントを遅らせるだけなので、戻るとすべてのイベントが発生しますが、一度にすべてが...

実用的なアプリケーションは、単純なスライドショーのためのこの問題です。数字が画像であることを想像してください。ユーザーが戻ってきたときにタブを非表示にしておくと、すべての画像が浮かんでいるのを見ることができます。

これを修正するには、pimvdbのようにstop(true、true)を使用します。これにより、イベントキューがクリアされます。

5
1st4ck

私にとって、ここで他の人のようにバックグラウンドでオーディオを再生することは重要ではありません。私の問題は、私がいくつかのアニメーションを持ち、他のタブにいてそれらに戻ったときに狂ったように振る舞うことでした。私の解決策は、これらのアニメーションをifに配置することでした非アクティブなタブ

if (!document.hidden){ //your animation code here }

おかげで私のアニメーションは実行されていましたonlyタブがアクティブだった場合。これが私のケースで誰かを助けることを願っています。

2
tomanolatino

Ruslan Tushovのライブラリの影響を強く受けて、私は自分の小さな library を作成しました。 <head>にスクリプトを追加するだけで、setIntervalsetTimeoutWebWorkerを使用するパッチが適用されます。

1
Mariy

これが私の大まかな解決策です

(function(){
var index = 1;
var intervals = {},
    timeouts = {};

function postMessageHandler(e) {
    window.postMessage('', "*");

    var now = new Date().getTime();

    sysFunc._each.call(timeouts, function(ind, obj) {
        var targetTime = obj[1];

        if (now >= targetTime) {
            obj[0]();
            delete timeouts[ind];
        }
    });
    sysFunc._each.call(intervals, function(ind, obj) {
        var startTime = obj[1];
        var func = obj[0];
        var ms = obj[2];

        if (now >= startTime + ms) {
            func();
            obj[1] = new Date().getTime();
        }
    });
}
window.addEventListener("message", postMessageHandler, true);
window.postMessage('', "*");

function _setTimeout(func, ms) {
    timeouts[index] = [func, new Date().getTime() + ms];
    return index++;
}

function _setInterval(func, ms) {
    intervals[index] = [func, new Date().getTime(), ms];
    return index++;
}

function _clearInterval(ind) {
    if (intervals[ind]) {
        delete intervals[ind]
    }
}
function _clearTimeout(ind) {
    if (timeouts[ind]) {
        delete timeouts[ind]
    }
}

var intervalIndex = _setInterval(function() {
    console.log('every 100ms');
}, 100);
_setTimeout(function() {
    console.log('run after 200ms');
}, 200);
_setTimeout(function() {
    console.log('closing the one that\'s 100ms');
    _clearInterval(intervalIndex)
}, 2000);

window._setTimeout = _setTimeout;
window._setInterval = _setInterval;
window._clearTimeout = _clearTimeout;
window._clearInterval = _clearInterval;
})();
0
Tengiz

注:このソリューションは、オーディオの再生などの間隔がバックグラウンドで機能する場合は適切ではありませんが、たとえば、戻ってきたときにアニメーションが正しく動作しないなどの混乱がある場合あなたのページ(タブ)これは良い解決策です。

この目標を達成するには多くの方法があります。「WebWorkers」が最も標準的なものかもしれませんただしこの方法を試してください:

►基本概念:

1-間隔(またはアニメーション)の名前を作成し、間隔(アニメーション)を設定します。これにより、ユーザーが初めてページを開いたときに実行されます:var interval_id = setInterval(your_func, 3000);

2- $(window).focus(function() {});および$(window).blur(function() {});clearInterval(interval_id)ブラウザー(タブ)が非アクティブになり、ブラウザー(タブ)がinterval_id = setInterval();で再びアクティブになるたびに間隔(アニメーション)を再実行できます

►サンプルコード:

var interval_id = setInterval(your_func, 3000);

$(window).focus(function() {
    interval_id = setInterval(your_func, 3000);
});
$(window).blur(function() {
    clearInterval(interval_id);
    interval_id = 0;
});
0

setIntervalrequestAnimationFrameの両方は、タブが非アクティブまたは動作しているときは動作しますが、適切な期間では動作しません。解決策は、タイムイベントに別のソースを使用することです。たとえば、web socketsまたはweb workersは正常に機能する2つのイベントソースですタブは非アクティブです。したがって、すべてのコードをWebワーカーに移動する必要はなく、ワーカーをタイムイベントソースとして使用するだけです。

// worker.js
setInterval(function() {
    postMessage('');
}, 1000 / 50);

var worker = new Worker('worker.js');
var t1 = 0;
worker.onmessage = function() {
    var t2 = new Date().getTime();
    console.log('fps =', 1000 / (t2 - t1) | 0);
    t1 = t2;
}

jsfiddle link このサンプルの。

0
iman

オーディオファイルを再生すると、当分の間、バックグラウンドでのJavaScriptの完全なパフォーマンスが保証されます。

私にとって、それは最も単純で邪魔にならない解決策でした-かすかな/ほとんど空の音を再生することを除いて、他の潜在的な副作用はありません

詳細はこちらで確認できます: https://stackoverflow.com/a/51191818/914546

(他の回答から、一部の人々はAudioタグの異なるプロパティを使用していることがわかりますが、実際に何かを再生せずにAudioタグを完全なパフォーマンスのために使用できるかどうか疑問に思います)

0
Kaan Soral

jQuery 3.3.xでこれを取得する最も簡単な方法は、この関数を使用することだと思います。

intervalFunction();

$([window, document]).on('focusin', function(){
    if (!intervalId){
        intervalFunction();
    }
}).on('focusout', function(){
    if (intervalId) {
        clearInterval(intervalId);
        intervalId = undefined;
    }
});

function intervalFunction() {
    // Your function hire
}

intervalFunction()はページのロード時に即座に初期化され、フォーカスが戻った場合は間隔を再アクティブ化しようとしますが、ページがフォーカスを失った場合はいつでも元に戻すことができます。

それは明確で簡単です。さらに、このソリューションでは、.focus().focusin()、_.focusout()などの非推奨の関数は使用しません。

0
kris_IV