Как сделать setInterval также работать, когда вкладка неактивна в Chrome?

у меня есть 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, если вкладка неактивна.

Я думаю, что это делается по дизайну, чтобы улучшить производительность, но есть ли способ отключить это поведение? Это на самом деле недостаток в моем случае.

9 ответов


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

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

здесь vanilla 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>

для фоновых задач (не связанных с UI)

@UpTheCreek комментарий:

отлично подходит для вопросов презентации, но все же есть некоторые вещи, которые вам нужно продолжать работать.

если у вас есть фоновые задачи, что должен быть точно исполненным с заданными интервалами, вы может использовать HTML5 веб-работники. Взгляните на ответ мере ниже для получения более подробной информации...

CSS против JS "анимации"

эта проблема и и многие другие можно избежать, используя CSS-переходы / анимации вместо анимации на основе JavaScript, что добавляет значительные накладные расходы. Я бы рекомендовал это плагин jQuery это позволит вам воспользоваться CSS-переходами так же, как animate() методы.


я столкнулся с той же проблемой с замиранием звука и HTML5-плеером. Он застрял, когда tab стал неактивным. Поэтому я узнал, что веб-работнику разрешено использовать интервалы / таймауты без ограничений. Я использую его для публикации "ТИКов" в основном 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;
    }
});

существует решение для использования веб-работников (как упоминалось ранее), потому что они работают в отдельном процессе и не замедляются

Я написал крошечный скрипт, который можно использовать без изменений в вашем коде-он просто переопределяет функции setTimeout, clearTimeout, setInterval, clearInterval.

просто включите его перед всем вашим кодом.

подробнее здесь


просто сделать это:

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 мс, которые он использовал для зажима).


Я думаю, что лучшее понимание этой проблемы заключается в следующем примере:http://jsfiddle.net/TAHDb/

Я делаю здесь простую вещь:

имеют интервал 1 сек и каждый раз скрывают первый пролет и переместить его на последний, и показать 2-й пролет.

Если вы остаетесь на странице, она работает так, как предполагается. Но если вы спрячете вкладку на несколько секунд, когда вы вернетесь, вы увидите проветренную вещь.

Это как все события, которые не ucur в то время, когда вы были неактивны, теперь будет ocur все в 1 раз. таким образом, в течение нескольких секунд вы получите как X событий. они настолько быстры, что можно увидеть все 6 пролетов сразу.

Так что швы chrome только задерживает события, так что когда вы вернетесь все события будут происходить, но все сразу...

практическое приложение было это ocur iss для простого слайд-шоу. Представьте, что числа являются изображениями, и если пользователь останется со скрытой вкладкой, когда он вернется, он увидит все imgs плавает, полностью загипнотизированный.

чтобы исправить это, используйте стоп (true, true), как сказал pimvdb. Это очистит очередь событий.


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


вот мое грубое решение

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

воспроизведение аудиофайла обеспечивает полную фоновую производительность Javascript в настоящее время

для меня это было самое простое и наименее навязчивое решение-кроме воспроизведения слабого / почти пустого звука, нет никаких других потенциальных побочных эффектов

вы можете найти детали здесь:https://stackoverflow.com/a/51191818/914546

(из других ответов я вижу, что некоторые люди используют разные свойства звукового тега, мне интересно, можно использовать аудио-тег для полной производительности, фактически ничего не играя)


Я смог вызвать свою функцию обратного вызова как минимум 250 мс, используя аудио-тег и обрабатывая его событие ontimeupdate. Его называют 3-4 раза в секунду. Его лучше, чем одна секунда отставания setTimeout