У меня есть 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, если вкладка неактивна.
Я предполагаю, что это сделано по дизайну, чтобы повысить производительность, но есть ли способ отключить это поведение? Это на самом деле недостаток в моем сценарии.
В большинстве браузеров неактивные вкладки выполняются с низким приоритетом, и это может повлиять на таймеры 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 вместо анимации на основе JavaScript, которая добавляет значительные накладные расходы. Я рекомендую этот плагин jQuery, который позволит вам воспользоваться преимуществами переходов CSS, как методы animate()
.
Я столкнулся с той же проблемой со звуковым затуханием и проигрывателем 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;
}
});
Существует решение использовать Web Workers (как упоминалось ранее), поскольку они работают в отдельном процессе и не замедляются
Я написал небольшой скрипт, который можно использовать без изменений в коде - он просто переопределяет функции 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 секунду и каждый раз скройте первый пролет и переместите его на последний, и покажите второй интервал.
Если вы остаетесь на странице, он работает так, как предполагается. Но если вы скроете вкладку в течение нескольких секунд, когда вы вернетесь, вы увидите внятную вещь.
Как и все события, которые не были убиты в то время, когда вы были неактивны, все будет происходить всего за 1 раз. поэтому в течение нескольких секунд вы получите как X-события. они настолько быстр, что можно увидеть все 6 пролетов сразу.
Таким образом, швы хром только задерживают события, поэтому, когда вы возвращаетесь, все события будут происходить, но все сразу...
Приоритетное приложение было для обычного слайд-шоу. Представьте, что цифры являются Изображениями, и если пользователь останется с скрытой вкладкой, когда он вернется, он увидит, что все imgs плавающие, Полностью mesed.
Чтобы исправить это, используйте stop (true, true), например pimvdb. Это очистит очередь событий.
requestAnimationFrame
который использовал jQuery. Из-за некоторых причуд (как этот), они удалили это. В 1.7 вы не видите такого поведения. jsfiddle.net/TAHDb/1 . Поскольку интервал составляет один раз в секунду, это фактически не влияет на опубликованную мной проблему, поскольку максимум для неактивных вкладок также составляет один раз в секунду, так что это не имеет значения.
Для меня не важно воспроизводить аудио в фоновом режиме, как для других здесь, моя проблема была в том, что у меня были некоторые анимации, и они действовали как сумасшедшие, когда вы были на других вкладках и возвращались к ним. Мое решение было поместить эти анимации внутрь, если это предотвращает неактивную вкладку:
if (!document.hidden){ //your animation code here }
благодаря этому моя анимация работала, только если была активна вкладка. Я надеюсь, что это поможет кому-то в моем случае.
setInterval(() => !document.hidden && myfunc(), 1000)
и он отлично работает
Сильно под влиянием библиотеки Руслана Тушова я создал свою небольшую библиотеку . Просто добавьте script в <head>
, и он будет патчем setInterval
и setTimeout
теми, которые используют WebWorker
.
Примечание: это решение не подходит, если вам нравится, когда ваш интервал работает на фоне, например, при воспроизведении аудио или... но если вы не уверены, например, что ваша анимация не работает должным образом при возврате на вашу страницу (вкладку), это хорошее решение.
Есть много способов достичь этой цели, может быть, "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;
});
И 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 этого образца.
Воспроизведение аудиофайла обеспечивает полное фоновое Javascript-производительность на данный момент
Для меня это было самое простое и наименее навязчивое решение - помимо воспроизведения слабого/почти пустого звука нет никаких других потенциальных побочных эффектов
Подробности можно найти здесь: https://stackoverflow.com/questions/6032429/chrome-timeouts-interval-suspended-in-background-tabs
(Из других ответов я вижу, что некоторые люди используют разные свойства тега Audio, я действительно задаюсь вопросом, можно ли использовать тег Audio для полной производительности, фактически не играя что-то)
Здесь мое грубое решение
(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;
})();
postMessageHandler
вызывает свое собственное событие, которое обрабатывает, тем самым имея бесконечный цикл, который не блокирует пользовательский интерфейс. И пока он бесконечно зацикливается, он проверяет каждую обработку событий, есть ли функция тайм-аута или интервала для запуска.
Мне удалось вызвать свою функцию обратного вызова минимум 250 мс с помощью звукового тега и обработать его событие ontimeupdate. Его вызывали 3-4 раза в секунду. Его лучше, чем одна секунда с задержкой setTimeout
Date
чтобы действительно увидеть, сколько времени прошло.