Jak sprawić, aby setInterval działał również, gdy karta jest nieaktywna w Chrome?

190

Mam setIntervaldziałający fragment kodu 30 razy na sekundę. Działa to świetnie, jednak gdy wybiorę inną kartę (aby karta z moim kodem stała się nieaktywna),setInterval jakiegoś powodu ustawia się ją w stan bezczynności.

Zrobiłem ten uproszczony przypadek testowy ( http://jsfiddle.net/7f6DX/3/ ):

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

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

Jeśli uruchomisz ten kod, a następnie przełączysz się na inną kartę, poczekaj kilka sekund i wróć, animacja będzie kontynuowana w momencie, w którym przełączyłeś się na inną kartę. Tak więc animacja nie jest uruchamiana 30 razy na sekundę w przypadku, gdy karta jest nieaktywna. Można to potwierdzić, licząc liczbę razysetInterval funkcji w każdej sekundzie - nie będzie to 30, ale tylko 1 lub 2, jeśli karta jest nieaktywna.

Wydaje mi się, że odbywa się to zgodnie z projektem, aby poprawić wydajność, ale czy jest jakiś sposób na wyłączenie tego zachowania? W moim scenariuszu jest to faktycznie wada.

pimvdb
źródło
3
Prawdopodobnie nie, chyba że zhakowałeś go razem z Dateobiektem, aby naprawdę zobaczyć, co minęło.
alex
Czy możesz wyjaśnić więcej o swoim scenariuszu? Może to nie jest taka wada.
James
2
Masz na myśli zmianę kodu i to, o co pytasz, jest również omawiane tutaj - Oliver Mattos opublikował trochę pracy, może jest to również ważne w twoim przypadku?
Shadow Wizard is Ear For You
6
Prawie zawsze najlepiej jest kluczować animacje w czasie rzeczywistym, jaki upłynął od początku animacji, zgodnie z treścią Date. Tak więc, gdy interwały nie uruchamiają się szybko (z tego lub innych powodów) animacja staje się bardziej gwałtowna, a nie wolniejsza.
bobince
1
Tytuł pytania doprowadził mnie tutaj, jednak mój przypadek użycia był nieco inny - potrzebowałem odświeżonego tokena uwierzytelniającego, niezależnie od bezczynności karty. Jeśli to dla ciebie istotne, sprawdź moją odpowiedź tutaj
Erez Cohen

Odpowiedzi:

153

W większości przeglądarek nieaktywne karty mają niski priorytet wykonywania, co może mieć wpływ na liczniki JavaScript.

Jeśli wartości przejścia zostały obliczone przy użyciu czasu rzeczywistego między ramkami zamiast ustalonych przyrostów w każdym interwale, można nie tylko obejść ten problem, ale także uzyskać płynniejszą animację za pomocą requestAnimationFrame, ponieważ może uzyskać do 60 kl./s, jeśli procesor nie jest bardzo zajęty.

Oto waniliowy przykład JavaScript animowanego przejścia właściwości za pomocą 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>


Do zadań w tle (niezwiązanych z interfejsem użytkownika)

@UpTheCreek komentarz:

W porządku w przypadku problemów z prezentacją, ale nadal musisz działać.

Jeśli masz zadania w tle, które muszą być precyzyjnie wykonywane w określonych odstępach czasu, możesz użyć HTML5 Web Workers . Spójrz na odpowiedź Möhre poniżejWięcej informacji ...

„Animacje” CSS vs JS

Tego problemu i wielu innych można uniknąć, stosując przejścia / animacje CSS zamiast animacji opartych na JavaScript, co powoduje znaczne obciążenie. Polecam tę wtyczkę jQuery, która pozwala czerpać korzyści z przejść CSS, podobnie jak animate()metody.

kbtzr
źródło
1
Próbowałem tego na FireFoxie 5 i „setInterva” wciąż działa, nawet jeśli karta nie jest aktywna. Mój kod w „setInterval” wyświetla pokaz slajdów za pomocą „animate ()”. Wygląda na to, że animacja jest ustawiana w kolejce przez FireFox. Kiedy wracam do zakładki, FireFox wyrzuca kolejkę natychmiast jedna po drugiej, co powoduje szybkie przenoszenie pokazu slajdów, dopóki kolejka nie będzie pusta.
nthpixel
@nthpixel dodałby .stop (zgodnie z odpowiedzią www) pomoc w sytuacji tat (jak za każdym razem
@gordatron - .stop nie pomaga w mojej sytuacji. Takie samo zachowanie. I teraz korzystam z FireFox 6.0.2. Zastanawiam się, czy w taki sposób jQuery implementuje .animate.
nthpixel
@cvsguimaraes - tak, dobra uwaga. Czy wiesz coś w specyfikacji, która gwarantuje, że pracownicy sieci będą działać nawet na karcie w tle? Jestem trochę zaniepokojony, że dostawcy przeglądarek arbitralnie zdecydują się też je
zablokować
Zmieniłbym ostatni wiersz kodu, aby czytał, before = now;aby uzyskać upływ czasu od początku zamiast końca poprzedniego wywołania. Tego zazwyczaj chcesz animować. Tak jak jest teraz, jeśli wykonanie funkcji interwału zajmuje 15 ms, elapsedTimeda kolejne 35 ms (zamiast 50 ms, co jest interwałem) przy następnym wywołaniu (gdy karta jest aktywna).
Jonas Berlin
71

Mam ten sam problem z wygaszaniem dźwięku i odtwarzaczem HTML5. Utknął, gdy karta stała się nieaktywna. Dowiedziałem się więc, że WebWorker może używać interwałów / limitów czasowych bez ograniczeń. Używam go do publikowania „tyknięć” w głównym javascript.

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

Główny 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
źródło
6
Schludny! Teraz miejmy nadzieję, że tego nie naprawią.
pimvdb
2
Wspaniały! To podejście powinno być stosowane, gdy potrzebujesz dokładnie timerów, ale nie tylko do rozwiązania niektórych problemów z animacją!
Konstantin Smolyanin
1
Zrobiłem z tym świetny metronom. Ciekawe, że wszystkie najpopularniejsze maszyny bębnowe html5 nie używają tej metody i zamiast tego używają gorszego standardowego setTimeout. skyl.github.io/grimoire/fretboard-prototype.html - Chrome lub Safari grają teraz najlepiej.
Skylar Saveland
„Naprawią” go, jeśli deweloper zacznie go nadużywać. Z rzadkimi wyjątkami Twoja aplikacja nie jest tak ważna, że ​​powinna zużywać zasoby w tle, aby zapewnić minimalną poprawę komfortu użytkowania.
Gunchars,
41

Istnieje rozwiązanie do korzystania z pracowników sieci Web (jak wspomniano wcześniej), ponieważ działają one w osobnym procesie i nie są spowalniane

Napisałem mały skrypt, którego można używać bez zmian w kodzie - po prostu przesłania funkcje setTimeout, clearTimeout, setInterval, clearInterval.

Po prostu dołącz go przed całym kodem.

więcej informacji tutaj

Rusłan Tuszow
źródło
1
Przetestowałem to w projekcie obejmującym biblioteki używające timerów (więc nie mogłem sam zaimplementować pracowników). Działa jak urok. Dzięki za tę bibliotekę
ArnaudR
@ ruslan-tushov właśnie wypróbowałem to na chrome 60, ale wydaje się, że w ogóle nie działa ... Wzywam kilka funkcji z kilku setTimeout, aby dodać / usunąć elementy DOM, które są animowane przez animacje css, i widzę, że są zamrożone z pewnym opóźnieniem po przełączeniu tabulatorów (jak zwykle bez wtyczek)
neoDev
@neoDev Czy ładujesz HackTimer.js (lub HackTimer.min.js) przed jakimkolwiek innym JavaScript?
Davide Patti,
@neoDev to brzmi naprawdę dziwnie, ponieważ ostatnio próbowałem i działa
Davide Patti,
@DurdenP tak Próbowałem również umieścić go przed jakimkolwiek innym JavaScript. Może dlatego, że korzystałem z animacji css? Pamiętam też, że próbowałem webmasterów, ale bez powodzenia
neoDev
13

Po prostu zrób to:

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

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

Nieaktywne karty przeglądarki buforują niektóre funkcje setIntervallub setTimeout.

stop(true,true) zatrzyma wszystkie buforowane zdarzenia i natychmiast wykona tylko ostatnią animację.

window.setTimeout()Metoda klejenia teraz wysłać nie więcej niż jeden czas oczekiwania na sekundę w nieaktywnych kart. Ponadto ogranicza teraz zagnieżdżone limity czasu do najmniejszej wartości dopuszczalnej przez specyfikację HTML5: 4 ms (zamiast 10 ms, przy której był zaciskany).

www
źródło
1
Warto o tym wiedzieć - na przykład w przypadku aktualizacji ajax, gdzie najnowsze dane są zawsze poprawne - ale w przypadku zadanego pytania nie oznacza to, że animacja została znacznie spowolniona / wstrzymana, ponieważ „a” nie byłby zwiększany odpowiednią liczbę razy ?
5

Myślę, że najlepiej rozumiem ten problem w tym przykładzie: http://jsfiddle.net/TAHDb/

Robię tutaj prostą rzecz:

Zachowaj odstęp 1 sekundy i za każdym razem ukryj pierwszy zakres, przesuń go do ostatniego i pokaż drugi zakres.

Jeśli zostaniesz na stronie, działa tak, jak powinno. Ale jeśli ukryjesz kartę na kilka sekund, po powrocie zobaczysz zużyty przedmiot.

To tak, jakby wszystkie zdarzenia, które nie wystąpiły w czasie, gdy byłeś nieaktywny, pojawią się za jednym razem. więc przez kilka sekund otrzymacie X wydarzeń. są tak szybkie, że można zobaczyć wszystkie 6 zakresów jednocześnie.

Dzięki temu chrom tylko opóźnia wydarzenia, więc kiedy wrócisz, wszystkie wydarzenia wystąpią, ale wszystkie naraz ...

Praktyczne zastosowanie to ta okazja do prostego pokazu slajdów. Wyobraź sobie, że liczby są obrazami, a jeśli użytkownik pozostanie z ukrytą kartą, gdy wróci, zobaczy wszystkie pływające obrazy, Całkowicie zmieszane.

Aby to naprawić, użyj stop (true, true), jak powiedział pimvdb. Spowoduje to wyczyszczenie kolejki zdarzeń.

1st4ck
źródło
4
W rzeczywistości dzieje się tak z powodu zastosowanego requestAnimationFramejQuery. Z powodu niektórych dziwactw, jakie miał (jak ten), usunęli go. W 1.7 nie widzisz tego zachowania. jsfiddle.net/TAHDb/1 . Ponieważ interwał jest raz na sekundę, tak naprawdę nie wpływa to na opublikowany przeze mnie problem, ponieważ maksimum dla nieaktywnych kart wynosi również raz na sekundę - więc to nie ma znaczenia.
pimvdb,
4

Dla mnie nie jest ważne, aby odtwarzać dźwięk w tle, tak jak dla innych tutaj, moim problemem było to, że miałem kilka animacji i działały jak szalone, gdy byłeś w innych kartach i wracałeś do nich. Moim rozwiązaniem było umieszczenie tych animacji wewnątrz, jeśli to uniemożliwia nieaktywną kartę :

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

dzięki temu moja animacja działała tylko przy aktywnej zakładce. Mam nadzieję, że to pomoże komuś w mojej sprawie.

Tomaaszq
źródło
1
Używam tego w ten sposób setInterval(() => !document.hidden && myfunc(), 1000)i działa idealnie
Didi Bear
4

Zarówno setIntervali requestAnimationFramenie działają, gdy karta jest nieaktywna lub pracy, ale nie w odpowiednich okresach. Rozwiązaniem jest użycie innego źródła zdarzeń czasowych. Na przykład gniazda sieciowe lub procesy sieciowe są dwoma źródłami zdarzeń, które działają dobrze, gdy karta jest nieaktywna. Nie trzeba więc przenosić całego kodu do pracownika sieci, wystarczy użyć pracownika jako źródła zdarzenia czasu:

// 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 do tej próbki.

iman
źródło
„po prostu użyj pracownika jako źródła zdarzenia czasowego” Dzięki za pomysł
Promod Sampath Elvitigala
1

Będąc pod silnym wpływem biblioteki Rusłana Tushova, stworzyłem własną małą bibliotekę . Po prostu dodaj skrypt do <head>i będzie łatał setIntervali setTimeouttymi, które go używają WebWorker.

Mariy
źródło
właśnie wypróbowałem go na chrome 60, ale wydaje się, że w ogóle nie działa ... Wzywam kilka funkcji z kilku setTimeout, aby dodać / usunąć elementy DOM, które są animowane przez animacje css, i widzę, że są zawieszone z pewnym opóźnieniem po przełącznik tab (jak zwykle bez wtyczek)
neoDev
Używam go z Chrome 60. Czy możesz zrobić demo i opublikować go na temat problemów z githubem?
Mariy
1

Odtwarzanie pliku audio zapewnia na razie pełną obsługę JavaScript w tle

Dla mnie było to najprostsze i najmniej inwazyjne rozwiązanie - poza odtwarzaniem słabego / prawie pustego dźwięku, nie ma innych potencjalnych efektów ubocznych

Szczegóły znajdziesz tutaj: https://stackoverflow.com/a/51191818/914546

(Z innych odpowiedzi wynika, że ​​niektórzy ludzie używają różnych właściwości tagu audio, zastanawiam się, czy można użyć tagu audio w celu uzyskania pełnej wydajności, bez faktycznego odtwarzania)

Kaan Soral
źródło
0

Uwaga: to rozwiązanie nie jest odpowiednie, jeśli podoba Ci się, że Twój interwał działa w tle, na przykład podczas odtwarzania dźwięku lub ... ale jeśli nie rozumiesz na przykład, że animacja nie działa poprawnie po powrocie na stronę (kartę) dobre rozwiązanie.

Istnieje wiele sposobów na osiągnięcie tego celu, być może „WebWorkers” jest najbardziej standardowy, ale na pewno nie jest to najłatwiejszy i najbardziej przydatny, zwłaszcza jeśli nie masz wystarczająco dużo czasu, więc możesz spróbować w ten sposób:

►PODSTAWOWA KONCEPCJA:

1- zbuduj nazwę interwału (lub animacji) i ustaw interwał (animację), aby był uruchamiany, gdy użytkownik po raz pierwszy otworzy stronę: var interval_id = setInterval(your_func, 3000);

2 obok $(window).focus(function() {});i $(window).blur(function() {});możesz clearInterval(interval_id)za każdym razem dezaktywować przeglądarkę (zakładkę) i ponownie uruchomić interwał (animację) za każdym razem, gdy przeglądarka (zakładka) będzie ponownie aktywowana przezinterval_id = setInterval();

►KOD PRZYKŁADOWY:

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
źródło
0

To dość stare pytanie, ale napotkałem ten sam problem.
Jeśli korzystasz z sieci na Chrome, możesz przeczytać ten wpis Karty w tle w Chrome 57 .

Zasadniczo licznik czasu może działać, jeśli nie zabraknie mu budżetu czasu.
Zużycie budżetu opiera się na zużyciu czasu procesora przez zadanie wewnątrz timera.
W oparciu o mój scenariusz rysuję wideo na płótnie i transportuję do WebRTC.
Połączenie wideo webrtc aktualizuje się, nawet karta jest nieaktywna.

Musisz jednak użyć setIntervalzamiast requestAnimationFrame.
nie jest to jednak zalecane do renderowania interfejsu użytkownika.

Lepiej byłoby słuchać visibilityChange zdarzenia i odpowiednio zmieniać mechanizm renderowania.

Poza tym możesz spróbować @ kaan-soral i powinno działać w oparciu o dokumentację.

鄭元傑
źródło
-1

Oto moje szorstkie rozwiązanie

(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
źródło
postMessageHandlerPowołuje własną imprezę jest magazynowe, tym samym mając nieskończoną pętlę, która nie blokuje UI. Podczas gdy nieskończenie zapętla się, przy każdej obsłudze zdarzeń sprawdza, czy jest dostępna funkcja limitu czasu lub funkcji interwału.
foobored
-1

Byłem w stanie wywołać moją funkcję oddzwaniania co najmniej 250 ms za pomocą znacznika audio i obsługi jego zdarzenia ontimeupdate. Nazywa się to 3-4 razy na sekundę. Jego opóźnienie jest lepsze niż jedna sekunda

użytkownik7356139
źródło