Как заставить setInterval также работать, когда вкладка неактивна в Chrome? - PullRequest
167 голосов
/ 08 мая 2011

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

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

Ответы [ 13 ]

140 голосов
/ 08 мая 2011

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

CSS против JS "animations"

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

66 голосов
/ 21 сентября 2012

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

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

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

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

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

13 голосов
/ 12 сентября 2011

Просто сделайте это:

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 голосов
/ 07 ноября 2011

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

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

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

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

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

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

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

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

2 голосов
/ 27 декабря 2018

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

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

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

1 голос
/ 17 августа 2017

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

0 голосов
/ 10 февраля 2019

Я думаю, что самый простой способ получить это сейчас с помощью 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()

.
0 голосов
/ 05 февраля 2019

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

Есть много способов достижения этой цели, возможно, «WebWorkers» является самым стандартным , но , конечно, это несамый простой и удобный, особенно если вам не хватает времени, поэтому вы можете попробовать это следующим образом:

►ОБЩАЯ КОНЦЕПЦИЯ:

1 - создайте имя дляваш интервал (или анимация) и установите интервал (анимацию), чтобы он работал, когда пользователь впервые открывает вашу страницу: var interval_id = setInterval(your_func, 3000);

2- на $(window).focus(function() {}); и $(window).blur(function() {}); вы можете clearInterval(interval_id)каждый раз браузер (вкладка) деактивируется и повторно запускает ваш интервал (анимация) каждый раз, когда браузер (вкладка) снова активируется на interval_id = setInterval();

►SAMPLE CODE:

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 голосов
/ 29 января 2019

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

...