Как сделать так, чтобы 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 - это ухо для тебя

6
Почти всегда лучше всего использовать анимацию по количеству реального времени, истекшего с начала анимации, как видно из Date. Таким образом, когда интервалы не запускаются быстро (по этой или другим причинам), анимация становится более резкой, а не медленной.
bobince

1
Название вопроса привело меня сюда, однако мой вариант использования был немного другим - мне нужно было обновить токен аутентификации независимо от неактивности вкладки. Если это актуально для вас, посмотрите мой ответ здесь
Эрез Коэн,

Ответы:


153

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

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


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

@nthpixel добавит .stop (в соответствии с ответом www) помощь в ситуации с тат (как каждый раз, когда будет

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

@cvsguimaraes - да, хорошая мысль. Знаете ли вы что-нибудь в спецификации, которая гарантирует, что веб-работники будут работать даже в фоновом режиме? Я немного обеспокоен тем, что поставщики браузеров будут произвольно решать, что делать с ними и в будущем ...
UpTheCreek

Я бы изменил следующую последнюю строку кода, чтобы прочитать, 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 - chrome или safari играют лучше всего прямо сейчас.
Skylar Saveland

Они "исправят" это, если разработчик начнет злоупотреблять этим. За редким исключением, ваше приложение не так важно, чтобы оно потребляло ресурсы в фоновом режиме, чтобы обеспечить минимальное улучшение работы пользователя.
Gunchars

41

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

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

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

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


1
Я проверил его на проекте, включающем библиотеки, которые использовали таймеры (поэтому я не мог сам реализовать рабочих). Оно работало завораживающе. Спасибо за эту библиотеку
ArnaudR

@ ruslan-tushov только что попробовал это на 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 секунду, и каждый раз скрывайте первый промежуток, перемещайте его до последнего и показывайте второй промежуток.

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

Как и все события, которые не произошли в то время, когда вы были неактивны, произойдут все за 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?
Mariy

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

-1

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.