Як я можу зробити так, щоб setInterval також працював, коли вкладка неактивна в Chrome?


190

У мене setIntervalпрацює шматочок коду 30 разів на секунду. Це чудово працює, проте коли я вибираю іншу вкладку (так що вкладка з моїм кодом стає неактивною), setIntervalдеяка причина встановлюється в режим очікування.

Я зробив цей спрощений тестовий випадок ( http://jsfiddle.net/7f6DX/3/ ):

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

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

Якщо ви запустите цей код, а потім перейдете на іншу вкладку, зачекайте кілька секунд і поверніться назад, анімація продовжується в той момент, який був під час переходу на іншу вкладку. Тому анімація не працює 30 разів на секунду, якщо вкладка неактивна. Це можна підтвердити, підраховуючи кількість разів, коли setIntervalфункція викликається щосекунди - це буде не 30, а лише 1 або 2, якщо вкладка неактивна.

Я здогадуюсь, що це робиться дизайном, щоб покращити продуктивність, але чи є спосіб відключити цю поведінку? Це фактично недолік у моєму сценарії.


3
Напевно, ні, якщо ви не зламали його разом із Dateоб’єктом, щоб дійсно побачити, який час минув.
алекс

Чи можете ви пояснити більше свого сценарію? Можливо, це не такий недолік.
Джеймс

2
Ви маєте на увазі цю зміну коду, і те, що ви запитуєте, також обговорюється тут - Олівер Маттос опублікував якусь роботу, можливо, це дійсно і у вашому випадку?
Shadow Wizard is Ear for You

6
Майже завжди найкраще набирати анімацію на кількість реального часу, що минув з початку анімації, як прочитано з Date. Так що, коли інтервали не спрацьовують швидко (з тих чи інших причин), анімація стає дедалі більше, а не повільніше.
bobince

1
Назва питання привела мене сюди, однак мій випадок використання був дещо іншим - мені знадобився маркер автентифікації оновлений незалежно від неактивності вкладки. Якщо це актуально для вас, ознайомтеся з моєю відповіддю тут
Ерез Коен

Відповіді:


153

У більшості браузерів неактивні вкладки мають низький пріоритет, і це може вплинути на таймери JavaScript.

Якщо значення вашого переходу були обчислені, використовуючи реальний час, пройдений між кадрами, а не фіксованими кроками на кожному інтервалі, ви не тільки вирішите цю проблему, але і зможете досягти більш плавної анімації, використовуючи requestAnimationFrame, оскільки він може отримати до 60 кадрів в секунду, якщо процесор не буде дуже зайнятий.

Ось приклад JavaScript ванільного JavaScript переходу анімованого властивості за допомогою 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>


Для фонових завдань (не пов’язаних з інтерфейсом користувача)

Коментар @UpTheCreek:

Чудово під час презентації, але все ж є деякі речі, які потрібно продовжувати працювати.

Якщо у вас є фонові завдання, які потрібно точно виконувати через задані проміжки часу, ви можете використовувати HTML5 Web Workers . Подивіться на відповідь Мьоре нижче щоб отримати докладнішу інформацію ...

"Анімації" CSS проти JS

Цю проблему та багато інших можна уникнути, використовуючи переходи / анімації CSS замість анімації на основі JavaScript, що додає значні витрати. Я рекомендую цей плагін jQuery, який дасть вам змогу скористатися переходами CSS так само, як і animate()методи.


1
Я спробував це на FireFox 5 і 'setInterva'l все ще працює, навіть коли вкладка не в фокусі. Мій код у "setInterval" демонструє слайд-шоу за допомогою "animate ()". Схоже, анімація чергає FireFox. Коли я повертаюсь назад на вкладку, FireFox вискакує чергу відразу одна за одною, в результаті чого слайд-шоу, що швидко рухається, поки черга не буде порожньою.
nthpixel

@nthpixel додасть .stop (відповідно до відповіді www) допомогу в ситуації, що склалася (як і кожен раз, коли буде очищення попередньої анімації)

@gordatron - .stop не допомагає моїй ситуації. Така ж поведінка. І я зараз на FireFox 6.0.2. Цікаво, чи це спосіб jQuery реалізує .animate.
nthpixel

@cvsguimaraes - так, хороший момент. Чи знаєте ви що-небудь у специфікації, яка гарантує, що веб-працівники працюватимуть навіть у вкладці фону? Я хвилююся, що постачальники браузерів довільно вирішать
зафіксувати

Я міняв би наступний останній рядок коду для читання, before = now;щоб отримати минулий час з початку замість закінчення попереднього дзвінка. Зазвичай це те, що ти хочеш, анімуючи речі. Як і зараз, якщо виконання функції інтервалу займає 15 мс, elapsedTimeнадасть 35 мс (замість 50 мс, що є інтервалом) наступного разу, коли він буде викликаний (коли вкладка активна).
Йонас Берлін

71

Я зіткнувся з тією самою проблемою із згасанням звуку та програвачем 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;
    }
});

6
Акуратно! Тепер будемо сподіватися, що вони цього не "виправлять".
pimvdb

2
Чудово! Цей підхід слід використовувати, коли вам потрібні таймери для роботи, а не просто для вирішення деяких проблем з анімацією!
Костянтин Смолянін

1
Я зробив з цим чудовий метроном. Цікаво, що всі найпопулярніші барабанні машини html5 не використовують цей метод, а натомість використовують нижчий регулярний setTimeout. skyl.github.io/grimoire/fretboard-prototype.html - хром чи сафарі відтворюють його найкраще, саме зараз.
Skylar Saveland

Вони "виправлять" його, якщо розробник почне зловживати ним. За рідкісними винятками, ваш додаток не настільки важливий, щоб витрачати ресурси у фоновому режимі, щоб забезпечити мінімальне поліпшення користувальницької роботи.
Gunchars

41

Існує рішення використовувати веб-робітників (як уже згадувалося раніше), оскільки вони працюють в окремому процесі і не сповільнюються

Я написав крихітний сценарій, який можна використовувати без змін у вашому коді - він просто перекриває функції setTimeout, clearTimeout, setInterval, clearInterval.

Просто включіть його перед усім кодом.

Більше інформації тут


1
Я перевірив це на проекті, включаючи бібліотеки, які використовували таймери (тому я не міг сам реалізувати працівників). Це спрацювало як шарм. Дякуємо за цю бібліотеку
ArnaudR

@ Руслан-тушов просто спробував це на chrome 60, але, здається, це не має ефекту ... Я закликаю кілька функцій з декількох setTimeout, щоб додати / видалити елементи DOM, які анімовані css-анімацією, і я бачу, як вони заморожені з деякою затримкою після перемикання вкладок (так само, як зазвичай, без плагінів)
neoDev

@neoDev Ви завантажуєте HackTimer.js (або HackTimer.min.js) перед будь-яким іншим JavaScript?
Девід Патті

@neoDev це звучить насправді дивно, тому що я нещодавно спробував, і це працює
Davide Patti

@DurdenP так, я намагався також поставити його перед будь-яким іншим JavaScript. Може тому, що я використовував css анімацію? Пам’ятаю також, я спробував веб-працівників, але без везіння
neoDev

13

Просто зробіть це:

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()метод затискає надсилати не більше одного тайм-ауту в секунду на неактивних вкладках. Крім того, тепер він затискає вкладені тайм-аути до найменшого значення, дозволеного специфікацією HTML5: 4 мс (замість 10 мс, яке він використовував для затиску).


1
Це добре знати - наприклад, для оновлень ajax, де останні дані завжди вірні - але для розміщеного питання це не означає, що анімація була значно сповільнена / призупинена, оскільки "a" не було б збільшено відповідну кількість разів ?

5

Я думаю, що найкраще розуміння цієї проблеми є в цьому прикладі: http://jsfiddle.net/TAHDb/

Я тут роблю просту річ:

Майте інтервал в 1 сек і кожен раз приховуйте перший проміжок і переміщуйте його до останнього та показуйте другий проміжок.

Якщо ви залишаєтесь на сторінці, вона працює як належить. Але якщо ви приховати вкладку на кілька секунд, коли повернетесь, ви побачите стомлену річ.

Як і всі події, які не відбулися за той час, коли ви були неактивними, зараз відбудуться всі за один раз. тож за кілька секунд ви отримаєте як X події. вони настільки швидкі, що відразу можна побачити всі 6 прольотів.

Отже, хром лише затримує події, тож коли ви повернетесь, усі події відбудуться, але всі відразу ...

Цей практичний додаток був простим слайд-шоу. Уявіть, що цифри є зображеннями, і якщо користувач залишиться з прихованою вкладкою, коли він повернеться, він побачить, що всі картинки пливуть, цілком перемішані.

Щоб виправити це, використовуйте стоп (true, true), як сказав pimvdb. Це очистить чергу подій.


4
Насправді це через requestAnimationFrameте, що використовується jQuery. Через деякі примхи, які він мав (як цей), вони його зняли. У 1.7 ви не бачите такої поведінки. jsfiddle.net/TAHDb/1 . Оскільки інтервал є раз на секунду, це фактично не впливає на проблему, яку я розмістив, оскільки максимум для неактивних вкладок також один раз на секунду - так що це не має ніякої різниці.
pimvdb

4

Для мене не важливо відтворювати аудіо у фоновому режимі, як для інших, моя проблема полягала в тому, що у мене були анімації, і вони діяли як божевільні, коли ти знаходишся в інших вкладках і повертався до них. Моє рішення було помістити ці анімації всередину, якщо це запобігає неактивній вкладці :

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

завдяки цьому моя анімація працювала лише в тому випадку, якщо вкладка була активною. Я сподіваюся, що це допоможе комусь із моєї справи.


1
Я використовую це так, setInterval(() => !document.hidden && myfunc(), 1000)і він прекрасно працює
ведмідь Діді

4

І те, setIntervalі requestAnimationFrameінше не працює, коли вкладка неактивна або працює, але не в потрібні періоди. Рішенням є використання іншого джерела для подій у часі. Наприклад, веб-розетки або веб-працівники - це два джерела подій, які добре працюють, коли вкладка неактивна. Тому не потрібно переміщувати весь код до веб-працівника, просто використовуйте працівника як джерело подій у часі:

// 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 посилання цього зразка.


"просто використовуйте працівника як джерело подій у часі" Дякую за ідею
Promod Sampath Elvitigala

1

Сильно впливаючи на бібліотеку Руслана Тушова, я створив власну невелику бібліотеку . Просто додайте скрипт у папку <head>та він буде виправлений setIntervalта setTimeoutз тими, які використовують WebWorker.


я просто спробував це на chrome 60, але, здається, це взагалі не має ефекту ... Я закликаю кілька функцій з декількох setTimeout, щоб додати / видалити елементи до DOM, які анімовані css-анімацією, і я бачу, що вони замерзли з деякою затримкою після перемикач на вкладку (як завжди без плагінів)
neoDev

Я використовую його з Chrome 60. Чи можете ви зробити демо-версію та опублікувати її на проблемах github?
Марій

1

Відтворення аудіофайлу забезпечує на сьогоднішній день повну фонову продуктивність Javascript

Для мене це було найпростішим і найменш настирливим рішенням - крім відтворення слабкого / майже порожнього звуку, інших потенційних побічних ефектів немає.

Деталі ви можете знайти тут: https://stackoverflow.com/a/51191818/914546

(З інших відповідей я бачу, що деякі користуються різними властивостями тегу Audio. Мені цікаво, чи можливо використовувати тег Audio для повної продуктивності, фактично не відтворюючи щось)


0

Примітка: це рішення не підходить, якщо вам подобається, що ваш інтервал працює на задньому плані, наприклад, відтворює аудіо або ..., але якщо вас бентежить, наприклад, що ваша анімація не працює належним чином при поверненні на вашу сторінку (вкладку), це хороше рішення.

Є багато способів досягти цієї мети, можливо, "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

Це досить старе питання, але я зіткнувся з тим же питанням.
Якщо ви працюєте в Інтернеті на хромі, ви можете прочитати цю публікацію Фон вкладки в Chrome 57 .

В основному таймер інтервалу може працювати, якщо у нього не закінчився бюджет таймера.
Споживання бюджету базується на використанні процесором часу завдання в таймері.
На основі мого сценарію я малюю відео на полотні та транспортую до WebRTC.
Відеозв'язок webrtc постійно оновлюватиметься, навіть вкладка неактивна.

Однак ви повинні використовувати setIntervalзамість цього requestAnimationFrame.
він не рекомендується використовувати для надання інтерфейсу користувача.

Було б краще прослухати visibilityChangeподію і змінити відповідно мехенізм.

Крім того, ви можете спробувати @ kaan-soral, і він повинен працювати на основі документації.


-1

Ось моє грубе рішення

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

postMessageHandlerВикликає своє власне подія він обробка, тим самим нескінченний цикл , який не блокує призначений для користувача інтерфейс. І хоча він нескінченно циклічний, він перевіряє кожну обробку подій, чи є функція тайм-ауту або інтервалу для запуску.
затримався

-1

Мені вдалося зателефонувати на функцію зворотного дзвінка як мінімум за 250 мс за допомогою звукового тегу та обробки його події оновлення оновлення. Його дзвонили 3-4 рази за секунду. Це краще, ніж на одну секунду відставання setTimeout

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.