it-swarm.com.de

Wie kann ich setInterval auch funktionieren lassen, wenn eine Registerkarte in Chrome inaktiv ist?

Ich habe eine setInterval, die 30 Mal pro Sekunde einen Code ausführt. Dies funktioniert hervorragend, wenn ich jedoch eine andere Registerkarte auswähle (damit die Registerkarte mit meinem Code inaktiv wird), wird die Variable setInterval aus irgendeinem Grund in den Ruhezustand versetzt.

Ich habe diesen vereinfachten Testfall erstellt ( http://jsfiddle.net/7f6DX/3/ ):

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

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

Wenn Sie diesen Code ausführen und dann zu einer anderen Registerkarte wechseln, einige Sekunden warten und zurückgehen, wird die Animation an dem Punkt fortgesetzt, an dem Sie zu der anderen Registerkarte gewechselt haben. Die Animation wird also nicht 30 Mal pro Sekunde ausgeführt, falls die Registerkarte inaktiv ist. Dies kann durch Zählen der Anzahl der Aufrufe der setInterval-Funktion pro Sekunde bestätigt werden - dies ist nicht 30, sondern nur 1 oder 2, wenn die Registerkarte inaktiv ist.

Ich denke, dass dies von Entwurf aus getan wird, um die Leistung zu verbessern, aber gibt es eine Möglichkeit, dieses Verhalten zu deaktivieren? Es ist tatsächlich ein Nachteil in meinem Szenario.

159
pimvdb

In den meisten Browsern haben inaktive Registerkarten eine niedrige Priorität, was sich auf JavaScript-Timer auswirken kann.

Wenn die Werte Ihres Übergangs mit real time zwischen den Bildern und nicht mit festen Inkrementen für jedes Intervall berechnet wurden, können Sie dieses Problem nicht nur umgehen, sondern können auch eine weichere Animation mithilfe von requestAnimationFrame as erzielen es kann bis zu 60 fps erreichen, wenn der Prozessor nicht sehr beschäftigt ist.

Hier ein Vanilla-JavaScript-Beispiel für einen animierten Eigenschaftsübergang mit requestAnimationFrame:

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>


Für Hintergrundaufgaben (nicht auf die Benutzeroberfläche bezogen)

@UpTheCreek-Kommentar:

Für Präsentationsprobleme geeignet, aber trotzdem Es gibt einige Dinge, die Sie benötigen, um weiterlaufen zu können.

Wenn Sie Hintergrundaufgaben haben, die benötigt in bestimmten Abständen präzise ausgeführt werden sollen, können Sie HTML5 Web Workers verwenden. Werfen Sie einen Blick auf Möhre's Antwort für weitere Details ...

CSS vs JS "Animationen"

Dieses Problem und viele andere könnten vermieden werden, indem CSS-Übergänge/Animationen anstelle von JavaScript-basierten Animationen verwendet werden, was einen erheblichen Mehraufwand verursacht. Ich würde dieses jQuery-Plugin empfehlen, mit dem Sie CSS-Übergänge genauso wie die animate()-Methoden nutzen können.

138
kbtzr

Ich hatte das gleiche Problem mit Audio-Fading und HTML5-Player. Es blieb hängen, als der Tab inaktiv wurde ..__ Ich fand heraus, dass ein WebWorker Intervalle/Zeitlimits ohne Einschränkungen verwenden darf. Ich verwende es, um "Ticks" im Haupt-Javascript zu posten.

Webworker-Code:

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);

Haupt-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;
    }
});
64
Möhre

Es gibt eine Lösung für die Verwendung von Web Workern (wie zuvor erwähnt), da sie in einem separaten Prozess ausgeführt werden und nicht verlangsamt werden

Ich habe ein kleines Skript geschrieben, das ohne Änderungen an Ihrem Code verwendet werden kann - es überschreibt einfach die Funktionen setTimeout, clearTimeout, setInterval und clearInterval.

Fügen Sie es einfach vor Ihrem gesamten Code ein.

mehr Infos hier

33
Ruslan Tushov

Mach einfach das:

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

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

Inaktive Browser-Registerkarten speichern einige der Funktionen setInterval oder setTimeout.

stop(true,true) stoppt alle gepufferten Ereignisse und führt sofort nur die letzte Animation aus.

Die window.setTimeout()-Methode klemmt jetzt so, dass in inaktiven Registerkarten nicht mehr als ein Timeout pro Sekunde gesendet wird. Darüber hinaus werden jetzt verschachtelte Timeouts auf den kleinsten Wert gekürzt, der in der HTML5-Spezifikation zulässig ist: 4 ms (anstelle der 10 ms, an die sie geklemmt wurden).

13
www

Ich denke, das beste Verständnis für dieses Problem ist in diesem Beispiel: http://jsfiddle.net/TAHDb/

Ich mache hier eine einfache Sache: 

Halten Sie ein Intervall von 1 Sekunde und verbergen Sie jedes Mal den ersten Bereich, verschieben Sie ihn zum letzten und zeigen Sie den zweiten Bereich an.

Wenn Sie auf der Seite bleiben, funktioniert es wie erwartet. Wenn Sie die Registerkarte für einige Sekunden ausblenden, werden Sie bei Ihrer Rückkehr etwas Weicheres sehen.

Es ist wie bei allen Ereignissen, die während der Zeit, in der Sie jetzt nicht aktiv waren, nicht durchgeführt wurden, alles in einem Mal. Für einige Sekunden werden Sie also wie X-Events aussehen. Sie sind so schnell, dass Sie alle 6 Felder gleichzeitig sehen können.

Es scheint also, dass Chrom nur die Ereignisse verzögert. Wenn Sie also zurückkommen, werden alle Ereignisse auf einmal ...

Eine praktische Anwendung war dies für eine einfache Diaschau. Stellen Sie sich vor, dass die Zahlen Bilder sind, und wenn der Benutzer mit dem Tab versteckt bleibt, wenn er zurückkommt, werden alle Bilder schwebend angezeigt, Totally mesed.

Um dies zu beheben, verwenden Sie den Stop (true, true) wie pimvdb sagte . Dadurch wird die Ereigniswarteschlange gelöscht.

5
1st4ck

Für mich ist es nicht wichtig, Audio im Hintergrund zu spielen, wie für andere hier. Mein Problem war, dass ich einige Animationen hatte und sie sich wie verrückt verhalten haben, wenn Sie in anderen Registerkarten waren und zu ihnen zurückkehren. Meine Lösung bestand darin, diese Animationen in zu setzen, wenn das inaktive Tab verhindert:

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

dank dessen lief meine Animation nur, wenn der Tab aktiv war. Ich hoffe, das hilft jemandem bei meinem Fall.

1
tomanolatino

Ich war stark von der Bibliothek von Ruslan Tushov beeinflusst und habe meine eigene kleine Bibliothek erstellt. Fügen Sie das Skript einfach in den <head> ein und es wird setInterval und setTimeout mit denjenigen gepatcht, die WebWorker verwenden.

1
Mariy

Hier ist meine grobe Lösung

(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

Hinweis: Diese Lösung ist nicht geeignet, wenn Sie möchten, dass Ihr Intervall im Hintergrund arbeitet, z. B. Audio abspielen oder ..., aber wenn Sie verwirrt sind, dass beispielsweise Ihre Animation nicht ordnungsgemäß funktioniert, wenn Sie zu Ihrer Seite zurückkehren ( tab) das ist eine gute lösung.

Es gibt viele Wege, um dieses Ziel zu erreichen, vielleicht sind die "WebWorkers" die meisten Standard, aber es ist sicherlich nicht die einfachste und praktischste, vor allem, wenn Sie nicht genug Zeit haben, so können Sie es versuchen :

►BASIC CONCEPT:

1- Erstellen Sie einen Namen für Ihr Intervall (oder Ihre Animation) und legen Sie Ihr Intervall (Animation) fest, damit es ausgeführt wird, wenn der Benutzer Ihre Seite zum ersten Mal öffnet: var interval_id = setInterval(your_func, 3000);

2- durch $(window).focus(function() {}); und $(window).blur(function() {}); können Sie clearInterval(interval_id) jedes Mal, wenn der Browser (Tab) deaktiviert ist, und das Intervall (Animation) erneut ausführen, wenn der Browser (Tab) erneut durch interval_id = setInterval(); aktiviert wird

►BEISPIELCODE:

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

Sowohl setInterval als auch requestAnimationFrame funktionieren nicht, wenn der Tab inaktiv ist oder arbeitet, aber nicht zu den richtigen Zeiten. Eine Lösung besteht darin, eine andere Quelle für Zeitereignisse zu verwenden. Beispielsweise sind web sockets oder web worker zwei Ereignisquellen, die problemlos funktionieren, wenn die Registerkarte inaktiv ist. Sie müssen also nicht den gesamten Code auf einen Web-Worker verschieben. Verwenden Sie Worker einfach als Quelle für Zeitereignisse:

// 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 dieses Beispiels.

0
iman

Durch das Abspielen einer Audiodatei wird zunächst eine vollständige JavaScript-Leistung im Hintergrund sichergestellt

Für mich war dies die einfachste und am wenigsten aufdringliche Lösung - abgesehen von einem schwachen/fast leeren Sound gibt es keine anderen möglichen Nebenwirkungen

Sie finden die Details hier: https://stackoverflow.com/a/51191818/914546

(Aus anderen Antworten sehe ich, dass einige Leute andere Eigenschaften des Audio-Tags verwenden. Ich frage mich, ob es möglich ist, das Audio-Tag für die volle Leistung zu verwenden, ohne tatsächlich etwas zu spielen.)

0
Kaan Soral

Ich denke, der einfachste Weg, dies jetzt mit jQuery 3.3.x zu bekommen, ist die Verwendung dieser Funktion:

intervalFunction();

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

function intervalFunction() {
    // Your function hire
}

intervalFunction() ist sofort beim Laden der Seite init, und wenn Sie den Fokus zurückgesetzt haben, versuchen Sie das Intervall erneut zu aktivieren. Wenn Ihre Seite jedoch den Fokus verliert, können Sie immer wieder zu diesem Punkt zurückkehren.

Es ist klar und leicht. Darüber hinaus verwendet diese Lösung keine veralteten Funktionen wie .focus(), .focusin() und .focusout().

0
kris_IV