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

158

У меня есть 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 чтобы действительно увидеть, сколько времени прошло.
  • 0
    Не могли бы вы объяснить больше о вашем сценарии? Может быть, это не такой недостаток.
Показать ещё 6 комментариев
Теги:
google-chrome
setinterval

12 ответов

136
Лучший ответ

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

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

Вот пример ванильного 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. Взгляните на ответ Möhre ниже для получения более подробной информации...

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

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

  • 1
    Я попробовал это на FireFox 5, и setInterva по-прежнему работает, даже когда вкладка не в фокусе. Мой код в «setInterval» слайд-шоу, используя «animate ()». Похоже, анимация ставится в очередь FireFox. Когда я возвращаюсь на вкладку, FireFox выдает очередь сразу один за другим, что приводит к быстрому слайд-шоу, пока очередь не станет пустой.
  • 0
    @nthpixel добавил бы .stop (согласно ответу www) помощь в ситуации с тат (как каждый раз, когда он очищал бы предыдущие анимации)
Показать ещё 3 комментария
64

Я столкнулся с той же проблемой со звуковым затуханием и проигрывателем 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;
    }
});
  • 4
    Ухоженная! Теперь давайте надеяться, что они не "исправят" это.
  • 2
    Большой! Этот подход следует использовать, когда вам нужны именно таймеры, а не только для устранения некоторых проблем с анимацией!
Показать ещё 1 комментарий
33

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

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

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

больше информации здесь

  • 1
    Я проверил это на проекте, включающем библиотеки, которые использовали таймеры (поэтому я не мог сам реализовать рабочих). Оно работало завораживающе. Спасибо за эту библиотеку
  • 0
    @ruslan-tushov ruslan-tushov только что попробовал это на chrome 60, но это, похоже, не имеет никакого эффекта ... Я вызываю несколько функций из нескольких setTimeout для добавления / удаления элементов в DOM, которые анимированы css-анимацией, и я вижу их зависшими с некоторой задержкой после переключения вкладок (как обычно, без плагинов)
Показать ещё 3 комментария
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 секунду и каждый раз скройте первый пролет и переместите его на последний, и покажите второй интервал.

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

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

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

Приоритетное приложение было для обычного слайд-шоу. Представьте, что цифры являются Изображениями, и если пользователь останется с скрытой вкладкой, когда он вернется, он увидит, что все imgs плавающие, Полностью mesed.

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

  • 4
    На самом деле это из-за requestAnimationFrame который использовал jQuery. Из-за некоторых причуд (как этот), они удалили это. В 1.7 вы не видите такого поведения. jsfiddle.net/TAHDb/1 . Поскольку интервал составляет один раз в секунду, это фактически не влияет на опубликованную мной проблему, поскольку максимум для неактивных вкладок также составляет один раз в секунду, так что это не имеет значения.
1

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

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

благодаря этому моя анимация работала, только если была активна вкладка. Я надеюсь, что это поможет кому-то в моем случае.

  • 0
    Я использую его следующим образом: setInterval(() => !document.hidden && myfunc(), 1000) и он отлично работает
1

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

  • 0
    только что попробовал это на chrome 60, но это, кажется, не имеет никакого эффекта ... Я вызываю несколько функций из нескольких setTimeout, чтобы добавить / удалить элементы в DOM, которые анимированы css-анимацией, и я вижу, что они зависли с некоторой задержкой после переключатель табуляции (как обычно, без плагинов)
  • 0
    Я использую его с Chrome 60. Можете ли вы сделать демо и опубликовать его на GitHub?
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

И 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 этого образца.

0

Воспроизведение аудиофайла обеспечивает полное фоновое Javascript-производительность на данный момент

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

Подробности можно найти здесь: https://stackoverflow.com/questions/6032429/chrome-timeouts-interval-suspended-in-background-tabs

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

0

Здесь мое грубое решение

(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\ 100ms');
    _clearInterval(intervalIndex)
}, 2000);

window._setTimeout = _setTimeout;
window._setInterval = _setInterval;
window._clearTimeout = _clearTimeout;
window._clearInterval = _clearInterval;
})();
  • 1
    Не могли бы вы объяснить, как работает ваше решение?
  • 0
    postMessageHandler вызывает свое собственное событие, которое обрабатывает, тем самым имея бесконечный цикл, который не блокирует пользовательский интерфейс. И пока он бесконечно зацикливается, он проверяет каждую обработку событий, есть ли функция тайм-аута или интервала для запуска.
-2

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню