Wie kann ich festlegen, dass setInterval auch funktioniert, wenn ein Tab in Chrome inaktiv ist?

189

Ich habe setInterval30 Mal pro Sekunde einen Code ausgeführt. Dies funktioniert jedoch hervorragend. Wenn ich jedoch eine andere Registerkarte auswähle (sodass die Registerkarte mit meinem Code inaktiv wird), setIntervalwird die Registerkarte 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 zur anderen Registerkarte gewechselt haben. Die Animation wird also nicht 30 Mal pro Sekunde ausgeführt, falls die Registerkarte inaktiv ist. Dies kann bestätigt werden, indem gezählt wird, wie oft die setIntervalFunktion pro Sekunde aufgerufen wird. Dies ist nicht 30, sondern nur 1 oder 2, wenn die Registerkarte inaktiv ist.

Ich denke, dass dies beabsichtigt ist, um die Leistung zu verbessern, aber gibt es eine Möglichkeit, dieses Verhalten zu deaktivieren? In meinem Szenario ist das eigentlich ein Nachteil.

pimvdb
quelle
3
Wahrscheinlich nicht, es sei denn, Sie haben es zusammen mit dem DateObjekt gehackt, um wirklich zu sehen, welche Zeit vergangen ist.
Alex
Können Sie mehr über Ihr Szenario erklären? Vielleicht ist es kein so großer Nachteil.
James
2
Du meinst diese Codeänderung, und was du fragst, wird auch hier besprochen - Oliver Mattos hat einige Arbeiten veröffentlicht, vielleicht ist es auch in deinem Fall gültig?
Schatten-Assistent ist Ohr für Sie
6
Es ist fast immer am besten, Animationen anhand der seit Beginn der Animation verstrichenen Echtzeit zu erfassen Date. Wenn die Intervalle (aus diesem oder anderen Gründen) nicht schnell ausgelöst werden, wird die Animation nur ruckeliger und nicht langsamer.
Bobince
1
Der Titel der Frage führte mich hierher, mein Anwendungsfall war jedoch etwas anders - ich musste ein Authentifizierungstoken aktualisieren, unabhängig von der Inaktivität eines Tabs. Wenn das für Sie relevant ist, lesen Sie meine Antwort hier
Erez Cohen

Antworten:

152

In den meisten Browsern haben inaktive Registerkarten eine Ausführung mit niedriger Priorität. Dies kann sich auf JavaScript-Timer auswirken.

Wenn die Werte Ihres Übergangs unter Verwendung von Echtzeit berechnet wurden, die zwischen Frames verstrichen ist, statt fester Inkremente in jedem Intervall, können Sie dieses Problem nicht nur umgehen, sondern auch eine flüssigere Animation erzielen, indem Sie requestAnimationFrame verwenden, da es bis zu 60 fps erreichen kann, wenn der Prozessor dies nicht tut sehr beschäftigt.

Hier ist ein Vanille-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 UI-bezogen)

@ UpTheCreek Kommentar:

Gut für Präsentationsprobleme, aber es gibt noch einige Dinge, die Sie ausführen müssen.

Wenn Sie Hintergrundaufgaben haben die Bedürfnisse genau in vorgegebenen Intervallen ausgeführt werden, können Sie mit HTML5 Web Worker . Schauen Sie sich Möhres Antwort unten an, um weitere Details zu erfahren ...

CSS vs JS "Animationen"

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

kbtzr
quelle
1
Ich habe dies in FireFox 5 versucht und 'setInterva'l läuft immer noch, auch wenn die Registerkarte nicht scharfgestellt ist. Mein Code in 'setInterval' schiebt eine Slidshow mit 'animate ()'. Es sieht so aus, als würde die Animation von FireFox in die Warteschlange gestellt. Wenn ich auf die Registerkarte zurückkehre, öffnet FireFox die Warteschlange sofort nacheinander, was zu einer sich schnell bewegenden Diashow führt, bis die Warteschlange leer ist.
nthpixel
@nthpixel würde hinzufügen .stop (gemäß der Antwort von www) Hilfe in tat Situation (wie jedes Mal, wenn es die vorherigen Animationen
@gordatron - .stop hilft meiner Situation nicht. Gleiches Verhalten. Und ich bin jetzt auf FireFox 6.0.2. Ich frage mich, ob jQuery auf diese Weise .animate implementiert.
nthpixel
@cvsguimaraes - ja guter Punkt. Kennen Sie irgendetwas in der Spezifikation, das garantiert, dass Web-Worker auch in einem Hintergrund-Tab ausgeführt werden? Ich bin ein bisschen besorgt, dass die Browser-Anbieter auch in Zukunft willkürlich entscheiden werden, diese zu klemmen ...
UpTheCreek
Ich würde die vorletzte Zeile des zu lesenden Codes ändern before = now;, um die seit dem Start verstrichene Zeit anstelle des Endes des vorherigen Aufrufs zu erhalten. Dies ist normalerweise das, was Sie wollen, wenn Sie Dinge animieren. Wenn eine Ausführung der Intervallfunktion elapsedTime15 ms dauert, ergibt sich beim nächsten Aufruf (wenn die Registerkarte aktiv ist) 35 ms (anstelle von 50 ms, was das Intervall ist).
Jonas Berlin
71

Ich hatte das gleiche Problem mit Audio-Fading und HTML5-Player. Es blieb hängen, als der Tab inaktiv wurde. Daher habe ich herausgefunden, dass ein WebWorker Intervalle / Timeouts ohne Einschränkung verwenden darf. Ich benutze es, um "Ticks" im Haupt-Javascript zu posten.

WebWorkers 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;
    }
});
Möhre
quelle
6
Ordentlich! Hoffen wir jetzt, dass sie das nicht "reparieren".
Pimvdb
2
Toll! Dieser Ansatz sollte verwendet werden, wenn Sie genau Timer benötigen, um zu arbeiten, aber nicht nur, um einige Animationsprobleme zu beheben!
Konstantin Smolyanin
1
Ich habe damit ein großartiges Metronom gemacht. Interessant, dass alle gängigen HTML5-Drumcomputer diese Methode nicht verwenden und stattdessen das minderwertige reguläre setTimeout verwenden. skyl.github.io/grimoire/fretboard-prototype.html - Chrome oder Safari spielen es gerade am besten.
Skylar Saveland
Sie werden es "reparieren", wenn Entwickler anfangen, es zu missbrauchen. Mit seltenen Ausnahmen ist Ihre App nicht so wichtig, dass sie im Hintergrund Ressourcen verbraucht, um die Benutzererfahrung nur minimal zu verbessern.
Gunchars
41

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

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

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

Mehr Infos hier

Ruslan Tushov
quelle
1
Ich habe es an einem Projekt getestet, das Bibliotheken enthält, die Timer verwenden (daher konnte ich selbst keine Mitarbeiter implementieren). Es funktionierte wie ein Zauber. Vielen Dank für diese Bibliothek
ArnaudR
@ ruslan-tushov hat es gerade auf Chrome 60 versucht, aber es scheint überhaupt keine Wirkung zu haben ... Ich rufe einige Funktionen aus mehreren setTimeout auf, um dem DOM Elemente hinzuzufügen / zu entfernen, die durch CSS-Animationen animiert werden, und ich sehe, dass sie eingefroren sind mit etwas Verzögerung nach dem Tab-Wechsel (wie immer ohne Plugins)
neoDev
@neoDev Laden Sie HackTimer.js (oder HackTimer.min.js) vor jedem anderen JavaScript?
Davide Patti
@neoDev es klingt wirklich seltsam, weil ich vor kurzem versucht habe und es funktioniert
Davide Patti
@ DurdenP ja ich habe auch versucht es vor jedes andere JavaScript zu stellen. Vielleicht, weil ich CSS-Animationen verwendet habe? Ich erinnere mich auch, dass ich Webworker ausprobiert habe, aber ohne Glück
neoDev
13

Mach das einfach:

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

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

Inaktive Browser-Registerkarten puffern einige setIntervaloder setTimeoutFunktionen.

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 eine Zeitüberschreitung pro Sekunde gesendet wird. Außerdem werden verschachtelte Zeitüberschreitungen jetzt auf den kleinsten Wert begrenzt, der in der HTML5-Spezifikation zulässig ist: 4 ms (anstelle der 10 ms, an die geklemmt wurde).

www
quelle
1
Dies ist gut zu wissen - zum Beispiel für Ajax-Updates, bei denen die neuesten Daten immer korrekt sind -, aber für die gestellte Frage würde dies nicht bedeuten, dass die Animation erheblich verlangsamt / angehalten wurde, da "a" nicht die entsprechende Anzahl von Malen erhöht worden wäre ?
5

Ich denke, dass ein bestes Verständnis für dieses Problem in diesem Beispiel besteht: http://jsfiddle.net/TAHDb/

Ich mache hier eine einfache Sache:

Halten Sie ein Intervall von 1 Sekunde ein und verbergen Sie jedes Mal die erste Spanne, verschieben Sie sie auf die letzte und zeigen Sie die zweite Spanne an.

Wenn Sie auf der Seite bleiben, funktioniert es wie angenommen. Wenn Sie die Registerkarte jedoch einige Sekunden lang ausblenden, sehen Sie bei Ihrer Rückkehr ein seltsames Element.

Es ist wie bei allen Ereignissen, die während der Zeit, in der Sie inaktiv waren, nicht aufgetreten sind, alles in einem Mal. Für einige Sekunden erhalten Sie also X-Ereignisse. Sie sind so schnell, dass alle 6 Bereiche gleichzeitig angezeigt werden können.

Chromnähte verzögern also nur die Ereignisse. Wenn Sie also zurückkommen, treten alle Ereignisse auf, aber alle auf einmal ...

Eine praktische Anwendung war dies für eine einfache Diashow. Stellen Sie sich vor, die Zahlen sind Bilder, und wenn der Benutzer bei seiner Rückkehr mit ausgeblendeten Registerkarten bleibt, werden alle Bilder schwebend angezeigt.

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

1st4ck
quelle
4
Tatsächlich liegt dies an der verwendeten requestAnimationFramejQuery. Aufgrund einiger Macken (wie dieser) haben sie es entfernt. In 1.7 sehen Sie dieses Verhalten nicht. jsfiddle.net/TAHDb/1 . Da das Intervall einmal pro Sekunde ist, wirkt sich dies nicht auf das von mir veröffentlichte Problem aus, da das Maximum für inaktive Registerkarten auch einmal pro Sekunde beträgt - das macht also keinen Unterschied.
Pimvdb
4

Für mich ist es nicht wichtig, Audio im Hintergrund abzuspielen, wie für andere hier. Mein Problem war, dass ich einige Animationen hatte und sie sich wie verrückt verhielten, wenn Sie sich in anderen Registerkarten befanden und zu ihnen zurückkehrten. Meine Lösung bestand darin, diese Animationen einzufügen, wenn dadurch ein inaktiver Tab verhindert wird :

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.

Tomaaszq
quelle
1
Ich benutze es so setInterval(() => !document.hidden && myfunc(), 1000)und es funktioniert perfekt
Didi Bear
4

Beides setIntervalund requestAnimationFramefunktioniert nicht, wenn die Registerkarte inaktiv ist oder funktioniert, aber nicht zum richtigen Zeitpunkt. Eine Lösung besteht darin, eine andere Quelle für Zeitereignisse zu verwenden. Beispielsweise sind Web-Sockets oder Web-Worker zwei Ereignisquellen, die einwandfrei funktionieren, wenn die Registerkarte inaktiv ist. Sie müssen also nicht Ihren gesamten Code auf einen Web-Worker verschieben, sondern nur Worker als Zeitereignisquelle verwenden:

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

Iman
quelle
"Verwenden Sie einfach Arbeiter als
Zeitereignisquelle
1

Ich bin stark von Ruslan Tushovs Bibliothek beeinflusst und habe meine eigene kleine Bibliothek erstellt . Fügen Sie einfach das Skript in das <head>und es wird gepatcht setIntervalund setTimeoutmit denen, die verwenden WebWorker.

Mariy
quelle
Ich habe es gerade auf Chrome 60 versucht, aber es scheint überhaupt keine Wirkung zu haben ... Ich rufe einige Funktionen aus mehreren setTimeout auf, um dem DOM Elemente hinzuzufügen / zu entfernen, die durch CSS-Animationen animiert werden, und ich sehe, dass sie mit einiger Verzögerung danach eingefroren sind Tab-Schalter (wie immer ohne Plugins)
NeoDev
Ich verwende es mit Chrome 60. Können Sie eine Demo erstellen und auf den Github-Problemen veröffentlichen?
Mariy
1

Durch das Abspielen einer Audiodatei wird vorerst die volle Javascript-Hintergrundleistung sichergestellt

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

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

(Aus anderen Antworten geht hervor, dass einige Benutzer unterschiedliche 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 abzuspielen.)

Kaan Soral
quelle
0

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;
})();
Tengiz
quelle
Der postMessageHandlerruft sein eigenes Ereignis auf, das er behandelt, wodurch eine Endlosschleife entsteht, die die Benutzeroberfläche nicht blockiert. Und während es sich um eine Endlosschleife handelt, prüft es bei jeder Ereignisbehandlung, ob eine Timeout- oder Intervallfunktion ausgeführt werden muss.
Foobored
0

Hinweis: Diese Lösung ist nicht geeignet, wenn Sie möchten, dass Ihr Intervall im Hintergrund ausgeführt wird, z. B. beim Abspielen von Audio oder ... Wenn Sie jedoch verwirrt sind, dass Ihre Animation beim Zurückkehren zu Ihrer Seite (Registerkarte) nicht ordnungsgemäß funktioniert, ist dies der Fall eine gute Lösung.

Es gibt viele Möglichkeiten, um dieses Ziel zu erreichen. Vielleicht sind die "WebWorker" die Standardmethoden, aber sicherlich nicht die einfachsten und praktischsten, insbesondere wenn Sie nicht genug Zeit haben, können Sie dies folgendermaßen versuchen:

► GRUNDKONZEPT:

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-by $(window).focus(function() {});und $(window).blur(function() {});Sie können clearInterval(interval_id)jedes Mal, wenn der Browser (Tab) deaktiviert ist und Ihr Intervall (Animation) jedes Mal neu ausführen, wenn der Browser (Tab) wieder aktiv wirdinterval_id = setInterval();

►MUSTERCODE:

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;
});
ashkan nasirzadeh
quelle
0

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

intervalFunction();

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

function intervalFunction() {
    // Your function hire
}

intervalFunction() wird beim Laden der Seite sofort initialisiert. Wenn Ihr Fokus wieder vorhanden ist, versuchen Sie, das Intervall wieder zu aktivieren. Wenn Ihre Seite jedoch den Fokus verliert, können Sie jederzeit darauf zurückgreifen.

Es ist klar und einfach. Zusätzlich diese Lösungen nicht verwenden veraltete Funktion wie .focus(), .focusin()und.focusout()

kris_IV
quelle
0

Es ist eine ziemlich alte Frage, aber ich bin auf dasselbe Problem gestoßen.
Wenn Sie Ihr Web auf Chrome ausführen, können Sie diesen Beitrag Hintergrund-Registerkarten in Chrome 57 lesen .

Grundsätzlich könnte der Intervall-Timer ausgeführt werden, wenn das Timer-Budget nicht aufgebraucht ist.
Der Verbrauch des Budgets basiert auf der CPU-Zeitauslastung der Aufgabe innerhalb des Timers.
Basierend auf meinem Szenario zeichne ich Videos auf Leinwand und transportiere sie zu WebRTC.
Die webrtc-Videoverbindung wird ständig aktualisiert, auch wenn die Registerkarte inaktiv ist.

Sie müssen jedoch setIntervalanstelle von verwenden requestAnimationFrame.
Es wird jedoch nicht für das Rendern der Benutzeroberfläche empfohlen.

Es wäre besser, das visibilityChangeEreignis zuzuhören und den Render-Mechenismus entsprechend zu ändern.

Außerdem könnten Sie @ kaan-soral ausprobieren und es sollte basierend auf der Dokumentation funktionieren.

鄭元傑
quelle
-1

Ich konnte meine Rückruffunktion mit einem Audio-Tag von mindestens 250 ms aufrufen und das Ereignis ontimeupdate verarbeiten. Es wird 3-4 mal in einer Sekunde aufgerufen. Es ist besser als eine Sekunde hinter setTimeout

user7356139
quelle