Chrome'da bir sekme etkin olmadığında setInterval'ı nasıl çalıştırabilirim?


190

setIntervalSaniyede 30 kez bir kod parçası çalışan var . Bu harika çalışıyor, ancak başka bir sekme seçtiğimde (kodumla sekme devre dışı kaldığında), setIntervalbir nedenden dolayı boşta durumuna ayarlanmış.

Bu basitleştirilmiş test senaryosunu yaptım ( http://jsfiddle.net/7f6DX/3/ ):

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

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

Bu kodu çalıştırıp başka bir sekmeye geçerseniz, birkaç saniye bekleyin ve geri dönün, animasyon diğer sekmeye geçtiğiniz noktada devam eder. Bu nedenle, sekmenin etkin olmaması durumunda animasyon saniyede 30 kez çalışmıyor. Bu, setIntervalişlevin her saniye kaç kez çağrıldığını sayarak doğrulanabilir - bu 30 değil, sekme etkin değilse sadece 1 veya 2 olacaktır.

Bunun performansı artırmak için tasarımla yapıldığını tahmin ediyorum, ancak bu davranışı devre dışı bırakmanın herhangi bir yolu var mı? Aslında benim senaryomda bir dezavantaj.


3
DateNe zaman geçtiğini gerçekten görmek için nesneyle birlikte hacklemediyseniz, muhtemelen hayır .
alex

Senaryonuz hakkında daha fazla bilgi verebilir misiniz? Belki de öyle bir dezavantaj değil.
James

2
Bu kod değişikliğini kastediyorsunuz ve sorduğunuz şey burada da tartışılıyor - Oliver Mattos bazı çalışmalar yaptı, belki sizin durumunuzda da geçerli?
Gölge Sihirbazı Senin İçin Kulak

6
Animasyonun başlangıcından bu yana okunduğu gibi geçen gerçek zaman miktarına göre animasyonları kilitlemek neredeyse her zaman en iyisidir Date. Böylece, aralıklar hızlı bir şekilde tetiklenmediğinde (bu veya başka nedenlerle), animasyon daha yavaş değil, daha gerginleşir.
bobince

1
Sorunun başlığı beni buraya getirdi, ancak kullanım durumum biraz farklıydı - Bir sekmenin hareketsizliğine bakılmaksızın yenilenen bir kimlik doğrulama jetonuna ihtiyacım vardı. Bu sizin için
Erez Cohen

Yanıtlar:


153

Çoğu tarayıcıda etkin olmayan sekmelerin önceliği düşüktür ve bu JavaScript zamanlayıcılarını etkileyebilir.

Geçişinizin değerleri, her aralıkta sabit artışlar yerine çerçeveler arasında geçen gerçek zamanlı kullanılarak hesaplandıysa , yalnızca bu sorunu geçici olarak çözmekle kalmaz , aynı zamanda işlemci değilse 60 fps'ye kadar ulaşabileceği için requestAnimationFrame'i kullanarak daha yumuşak bir animasyon elde edebilirsiniz. çok meşgul.

Aşağıda, aşağıdakileri kullanan bir animasyonlu özellik geçişinin vanilya JavaScript örneği verilmiştir 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>


Arka Plan Görevleri için (kullanıcı arayüzü ile ilgili olmayan)

@UpTheCreek yorumu:

Sunum sorunları için iyi, ancak yine de çalışmaya devam etmeniz gereken bazı şeyler var.

O arka plan görevleri varsa ihtiyaçları tam olarak belirli aralıklarla yürütülecek kullanabileceğiniz HTML5 Web Workers . Daha fazla ayrıntı için Möhre'nin cevabına bir göz atın ...

CSS vs JS "animasyonlar"

Bu sorun ve diğer pek çok şey, önemli bir ek yük sağlayan JavaScript tabanlı animasyonlar yerine CSS geçişleri / animasyonları kullanılarak önlenebilir. Yöntemler gibi CSS geçişlerinden yararlanmanıza izin veren bu jQuery eklentisini tavsiye ederim animate().


1
Bunu FireFox 5'te denedim ve sekme odakta olmasa bile 'setInterva'l hala çalışıyor. 'SetInterval' içindeki kodum, 'animate ()' öğesini kullanarak bir slayt gösterisi kaydırır. Animasyonun FireFox tarafından kuyruğa alındığı anlaşılıyor. Sekmeye geri döndüğümde, FireFox kuyruğu arka arkaya hemen sırayla açar ve kuyruk boşalana kadar hızlı hareket eden bir slayt gösterisi oluşturur.
nthpixel

@nthpixel tat durumunda .stop (www'nin cevabına göre) yardımı eklerdi (önceki animasyonları her temizlediğinde olduğu gibi)

@gordatron - .stop durumuma yardım etmiyor. Aynı davranış. Ve şimdi FireFox 6.0.2'deyim. JQuery'nin .animate uygulamasını uygulayıp kullanmadığını merak ediyorum.
nthpixel

@cvsguimaraes - evet iyi bir nokta. Spesifikasyonda web çalışanlarının arka plan sekmesinde bile çalışacağını garanti eden herhangi bir şey biliyor musunuz? Tarayıcı satıcılarının gelecekte de bunları kelepçelemeye karar vereceğinden biraz endişeliyim ...
UpTheCreek

Kodun bir sonraki son satırını before = now;, başlangıç yerine bitiş yerine geçen süreyi almak için değiştiririmÖnceki çağrının . Bu genellikle bir şeyleri canlandırırken istediğiniz şeydir. Şu anda olduğu gibi, aralık işlevinin yürütülmesi 15ms elapsedTimealırsa, bir dahaki sefere çağrıldığında (sekme etkin olduğunda) 35ms (aralık olan 50ms yerine) verecektir.
Jonas Berlin

71

Ses solması ve HTML5 oynatıcı ile aynı problemle karşılaştım. Sekme devre dışı kaldığında sıkıştı. Bu yüzden bir WebWorker'ın sınırlama olmaksızın aralık / zaman aşımı kullanımına izin verildiğini öğrendim. Ben ana javascript "keneler" göndermek için kullanın.

WebWorkers Kodu:

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

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

6
Temiz! Şimdi umarım bunu "düzeltmeyecekler".
pimvdb

2
Harika! Bu yaklaşım, sadece bazı animasyon sorunlarını düzeltmek için değil, aynı zamanda zamanlayıcıların tam olarak çalışması için de kullanıldığında kullanılmalıdır!
Konstantin Smolyanin

1
Bununla büyük bir metronom yaptım. En popüler html5 tambur makinelerinin tümünün bu yöntemi kullanmaması ve bunun yerine düşük düzenli setTimeout'u kullanması ilginçtir. skyl.github.io/grimoire/fretboard-prototype.html - krom veya safari şu anda en iyi şekilde oynuyor.
Skylar Saveland

Geliştirici kötüye kullanmaya başlarsa "düzeltir". Nadir istisnalar dışında, uygulamanız kullanıcı deneyiminde minimum bir iyileşme sağlamak için arka planda kaynak tüketmesi gerektiği kadar önemli değildir.
Gunchars

41

Web Workers'ı kullanmak için bir çözüm var (daha önce belirtildiği gibi), çünkü ayrı bir süreçte çalışıyorlar ve yavaşlamıyorlar

Kodunuzda değişiklik yapmadan kullanılabilecek küçük bir komut dosyası yazdım - sadece setTimeout, clearTimeout, setInterval, clearInterval işlevlerini geçersiz kılar.

Tüm kodunuzdan önce ekleyin.

burada daha fazla bilgi


1
Zamanlayıcıları kullanan kütüphaneleri içeren bir projede test ettim (işçileri kendim uygulayamadım). Bir cazibe gibi çalıştı. Bu kütüphane için teşekkürler
ArnaudR

@ ruslan-tushov sadece krom 60 üzerinde denedi ama hiç etkisi yok gibi görünüyor ... css animasyonları ile animasyonlu DOM eklemek / kaldırmak için birkaç setTimeout birkaç işlev çağırıyorum ve onları donmuş görüyorum sekme anahtarından sonra biraz gecikmeli (eklenti olmadan her zamanki gibi)
neoDev

@neoDev HackTimer.js'yi (veya HackTimer.min.js) başka bir JavaScript'ten önce yüklüyor musunuz?
Davide Patti

@neoDev gerçekten garip geliyor, çünkü son zamanlarda denedim ve işe yarıyor
Davide Patti

@DurdenP evet Ayrıca herhangi bir JavaScript önce koymak için çalıştı. Belki css animasyonları kullandığım için mi? Ayrıca hatırlıyorum webworkers denedim ama şanssız
neoDev

13

Sadece bunu yap:

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

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

Etkin olmayan tarayıcı sekmeleri, setIntervalveya setTimeoutişlevlerinden bazılarını arabelleğe alır .

stop(true,true) arabelleğe alınan tüm olayları durdurur ve hemen yalnızca son animasyonu yürütür.

window.setTimeout()Yöntem artık aktif olmayan sekmelerde saniyede en fazla bir zaman aşımı göndermeye kıskaçları. Ayrıca, artık yuvalanmış zaman aşımlarını HTML5 belirtimi tarafından izin verilen en küçük değere sıkıştırır: 4 ms (sıkıştırmak için kullanılan 10 ms yerine).


1
Bunu bilmek iyidir - örneğin en son verilerin her zaman doğru olduğu ajax güncellemeleri için - ancak gönderilen soru için bu, animasyonun önemli sayıda yavaşladığı / duraklatıldığı anlamına gelmez, çünkü "a" uygun sayıda artırılmazdı ?

5

Bu sorun hakkında en iyi anlayışın bu örnekte olduğunu düşünüyorum: http://jsfiddle.net/TAHDb/

Burada basit bir şey yapıyorum:

1 saniyelik bir aralık bırakın ve her seferinde ilk açıklığı gizleyin ve sonuna kadar taşıyın ve 2. açıklığı gösterin.

Sayfada kalırsanız, sözde olduğu gibi çalışır. Ancak sekmeyi birkaç saniyeliğine gizlerseniz, geri döndüğünüzde garip bir şey görürsünüz.

Sanki şu an aktif olmadığın zamana kadar ucur etmeyen tüm olaylar gibi 1 defada gerçekleşecek. bu yüzden birkaç saniye boyunca X etkinlikleri gibi olacaksınız. o kadar hızlıdır ki, aynı anda 6 açıklığın tümünü görmek mümkündür.

Bu yüzden krom sadece olayları geciktirir, bu yüzden geri döndüğünüzde tüm olaylar gerçekleşir, ancak bir kerede ...

Pratik bir uygulama basit bir slayt gösterisi için bu ocur iss idi. Sayıların Görüntüler olduğunu hayal edin ve kullanıcı geri geldiğinde gizli sekmeyle kalırsa, tüm imglerin yüzdüğünü görecektir, Tamamen mesed.

Bunu düzeltmek için pimvdb'nin anlattığı gibi stop (true, true) kullanın. Bu olay kuyruğunu temizleyecektir.


4
Aslında bu requestAnimationFramekullanılan jQuery nedeniyle . Bazı gariplikler yüzünden (bunun gibi), kaldırdılar. 1.7'de bu davranışı görmüyorsunuz. jsfiddle.net/TAHDb/1 . Aralık saniyede bir kez olduğu için, bu, yayınladığım sorunu etkilemez, çünkü etkin olmayan sekmeler için maksimum da saniyede birdir - bu nedenle fark etmez.
pimvdb

4

Benim için burada diğerleri gibi arka planda ses çalmak önemli değil, benim sorunum bazı animasyonlar vardı ve diğer sekmelerdeyken ve onlara geri döndüklerinde çılgın gibi davrandılar. Benim çözümüm içindeki bu animasyonlar koyarak eğer o engelliyor inaktif sekmeyi :

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

Bu nedenle animasyonum yalnızca sekme etkinse çalışıyordu. Umarım bu benim durumumda birine yardımcı olur.


1
Bu şekilde kullanıyorum setInterval(() => !document.hidden && myfunc(), 1000)ve mükemmel çalışıyor
Didi Bear

4

Her ikisi de setIntervalve requestAnimationFramesekme etkin olmadığında veya çalışmadığında, ancak doğru dönemlerde çalışmaz. Çözüm, zaman olayları için başka bir kaynak kullanmaktır. Örneğin, web soketleri veya web çalışanları , sekme etkin değilken iyi çalışan iki olay kaynağıdır. Bu nedenle, tüm kodunuzu bir web çalışanına taşımanıza gerek yok, yalnızca bir zaman etkinliği kaynağı olarak worker (işçi) kaynağını kullanın:

// 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;
}

Bu örneğin jsfiddle bağlantısı .


"Sadece işçi bir zaman olay kaynağı olarak kullanın" Fikir için teşekkürler
Promod Sampath Elvitigala

1

Ruslan Tushov'un kütüphanesinden çok etkilenerek, kendi küçük kütüphanemi oluşturdum . Sadece betiği ekleyin ve <head>yama setIntervalve setTimeoutkullananlarla ekleyin WebWorker.


sadece krom 60 üzerinde denedim ama hiç etkisi yok gibi görünüyor ... Ben css animasyonları ile animasyonlu DOM eklemek / kaldırmak için birkaç setTimeout birkaç fonksiyonları çağırıyorum ve sonra biraz gecikme ile donmuş görüyorum sekme anahtarı (eklenti olmadan her zamanki gibi)
neoDev

Chrome 60 ile kullanıyorum. Bir demo yapabilir ve github sorunlarına gönderebilir misiniz?
Mariy

1

Bir ses dosyasını oynatmak şimdilik tam arka plan Javascript performansı sağlar

Benim için, en basit ve en az müdahaleci çözümdü - zayıf / neredeyse boş bir ses çalmanın yanı sıra, başka potansiyel yan etkileri de yok

Detayları burada bulabilirsiniz: https://stackoverflow.com/a/51191818/914546

(Diğer yanıtlardan, bazı kişilerin Ses etiketinin farklı özelliklerini kullandığını görüyorum, Ses etiketini gerçek bir şey oynatmadan tam performans için kullanmanın mümkün olup olmadığını merak ediyorum)


0

Not: aralığınızın arka planda çalışmasını isterseniz, örneğin ses çalma veya ... isterseniz bu çözüm uygun değildir, ancak örneğin, sayfanıza (sekme) geri döndüğünüzde animasyonunuzun düzgün çalışmadığı konusunda kafanız karıştıysa, bu iyi bir çözüm.

Bu hedefe ulaşmak için birçok yol vardır, belki de "WebWorkers" en standart olanıdır, ancak kesinlikle en kolay ve kullanışlı olanı değildir, özellikle de yeterli zamanınız yoksa, bu şekilde deneyebilirsiniz:

►Temel Konsept:

1- aralığınız (veya animasyon) için bir ad oluşturun ve aralığınızı (animasyon) ayarlayın, böylece kullanıcı sayfanızı ilk kez açtığında çalışır: var interval_id = setInterval(your_func, 3000);

2- tarafından $(window).focus(function() {});ve $(window).blur(function() {});yapabilecekleriniz clearInterval(interval_id)sizin aralık (animasyon) her tarayıcı (sekme) her tarayıcı (sekme) aktif birakildi ve ReRun tarafından tekrar koyan aktif olurinterval_id = setInterval();

►Örnek Kod:

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

Oldukça eski bir soru ama aynı sorunla karşılaştım.
Web'inizi krom üzerinde çalıştırırsanız, Chrome 57'deki bu yazı Arka Plan Sekmelerini okuyabilirsiniz .

Temel olarak aralık sayacı, zamanlayıcı bütçesinin bitmemiş olması durumunda çalışabilir.
Bütçe tüketimi, zamanlayıcı içindeki görevin CPU zamanı kullanımına dayanır.
Senaryomuma göre, tuval üzerine video çiziyorum ve WebRTC'ye aktarıyorum.
Webrtc video bağlantısı, sekme etkin olmasa bile güncellenmeye devam eder.

Ancak setIntervalbunun yerine kullanmak zorundasınız requestAnimationFrame.
yine de UI oluşturma için önerilmez.

visibilityChangeOlayı dinlemek ve mechenizmi buna göre değiştirmek daha iyi olurdu .

Ayrıca, @ kaan-soral'ı deneyebilirsiniz ve belgelere dayanarak çalışmalıdır.


-1

İşte benim kaba çözümüm

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

postMessageHandlerO suretle UI engellemediğinden sonsuz döngü sahip işleyen kendi olayını çağırır. Ve sonsuz döngü halindeyken, çalıştırılacak bir zaman aşımı veya aralık işlevi olup olmadığını her olay işleme ile kontrol eder.
18:20

-1

Ses etiketini kullanarak ve ontimeupdate olayını işleyerek en az 250 ms geri arama işlevimi çağırabiliyordum. Saniyede 3-4 kez denir. Bir saniyelik gecikme setinden daha iyi

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.