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

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

Ответ 1

В большинстве браузеров неактивные вкладки имеют низкий приоритет выполнения, и это может повлиять на таймеры 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>

Ответ 2

Я столкнулся с той же проблемой со звуковым затуханием и проигрывателем 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;
    }
});

Ответ 3

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

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

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

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

Ответ 4

Просто сделайте следующее:

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

Ответ 5

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

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

Сделайте интервал в 1 секунду и каждый раз скройте первый пролет и переместите его на последний, и покажите второй интервал.

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

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

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

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

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

Ответ 6

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

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

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

Ответ 7

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

Ответ 9

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

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

Подробности можно найти здесь: fooobar.com/questions/37920/...

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

Ответ 10

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

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

Ответ 11

Примечание: это решение не подходит, если вам нравится, когда ваш интервал работает на фоне, например, при воспроизведении аудио или... но если вы не уверены, например, что ваша анимация не работает должным образом при возврате на вашу страницу (вкладку), это хорошее решение.

Есть много способов достичь этой цели, может быть, "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;
});

Ответ 12

Я думаю, что самый простой способ получить это сейчас с помощью jQuery 3.3.x - это использовать эту функцию:

intervalFunction();

$([window, document]).on('focusin', function(){
    if (!intervalId){
        intervalFunction();
    }
}).on('focusout', function(){
    if (intervalId) {
        clearInterval(intervalId);
        intervalId = undefined;
    }
});

function intervalFunction() {
    // Your function hire
}

intervalFunction() запускается сразу при загрузке страницы, а затем, если ваш фокус возвращается, вы пытаетесь повторно активировать интервал, но если ваша страница теряет фокус, вы всегда можете вернуться к этому.

Это понятно и просто. Кроме того, это решение не использует устаревшую функцию, такую как .focus(), .focusin() и .focusout()

Ответ 13

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