我如何才能使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次。

我想这样做是为了提高系统性能,但是有什么方法可以禁用这种行为吗?

这在我看来是劣势。

187719 次浏览

在大多数浏览器上,不活跃的选项卡执行优先级低,这可能会影响JavaScript计时器。

如果你的转换值是使用帧与帧之间的实时时间来计算的,而不是在每个间隔上使用固定增量,你不仅可以解决这个问题,而且还可以通过使用< >强requestAnimationFrame < / >强来实现窒息动画,因为如果处理器不是很忙的话,它可以达到60fps。

下面是一个使用requestAnimationFrame实现动画属性转换的JavaScript示例:

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>


For Background Tasks (non-UI related)

@UpTheCreek comment:

Fine for presentation issues, but still there are some things that you need to keep running.

If you have background tasks that needs to be precisely executed at given intervals, you can use HTML5 Web Workers. Take a look at Möhre's answer below for more details...

CSS vs JS "animations"

This problem and many others could be avoided by using CSS transitions/animations instead of JavaScript based animations which adds a considerable overhead. I'd recommend this jQuery plugin that let's you take benefit from CSS transitions just like the animate() methods.

只要这样做:

var $div = $('div');
var a = 0;


setInterval(function() {
a++;
$div.stop(true,true).css("left", a);
}, 1000 / 30);

非活动的浏览器选项卡缓冲了一些setIntervalsetTimeout函数。

stop(true,true)将停止所有缓冲的事件,只立即执行最后一个动画。

window.setTimeout()方法现在限制在非活动选项卡中每秒发送不超过一个超时。此外,它现在将嵌套超时限制到HTML5规范允许的最小值:4毫秒(而不是以前的10毫秒)。

我认为最好的理解这个问题是在这个例子:http://jsfiddle.net/TAHDb/

我在这里做了一件简单的事情:

间隔1秒,每次隐藏第一个跨度,并将其移动到最后,并显示第二个跨度。

如果你停留在页面上,它就像它应该的那样工作。 但是如果你隐藏标签几秒钟,当你回来的时候,你会看到一个奇怪的东西

这就像所有在你现在不活跃的时候没有发生的事情都会在一次发生一样。所以在几秒钟内你会看到X个事件。它们是如此之快,以至于有可能同时看到所有6个跨度。

所以它接缝铬只延迟事件,所以当你回来所有的事件将发生,但所有的一次…

这是一个简单的幻灯片的实际应用。想象数字是图像,如果用户保持标签隐藏,当他回来的时候,他会看到所有的imgs浮动,完全混乱。

要修复此问题,请像pimvdb那样使用stop(true,true)。 这将清除事件队列
我在音频衰落和HTML5播放器上也遇到了同样的问题。当标签变成非活动时,它被卡住了。 所以我发现WebWorker允许使用没有限制的间隔/超时。我用它来张贴“票据”。

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。

只需在所有代码之前包含它。

更多信息在这里

这是我的粗略解决方案

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

我能够调用我的回调函数在至少250ms使用音频标签和处理其ontimeupdate事件。它一秒钟被调用3-4次。这比一秒钟的滞后setTimeout要好

深受Ruslan Tushov库的影响,我创建了自己的小型图书馆。只要在<head>中添加脚本,它就会用使用WebWorker的脚本修补setIntervalsetTimeout

播放音频文件可以确保Javascript的完整后台性能

对我来说,这是最简单和侵入性最小的解决方案——除了播放微弱/几乎空的声音,没有其他潜在的副作用

你可以在这里找到详细信息:https://stackoverflow.com/a/51191818/914546

(从其他回答中,我看到一些人使用音频标签的不同属性,我想知道是否有可能使用音频标签的完整性能,而不实际播放一些东西)

对我来说,像其他人一样在后台播放音频并不重要,我的问题是我有一些动画,当你在其他选项卡中回到它们时,它们就像疯了一样。我的解决方案是把这些动画放在如果内,这是防止不活跃的选项卡:

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

由于我的动画正在运行只有如果标签是活动的。

.

.

setIntervalrequestAnimationFrame都不能在tab不活跃时工作,或者不能在正确的时间工作。一种解决方案是使用另一个时间事件源。例如,web socketsweb workers是两个在tab不活动时正常工作的事件源。所以不需要把你所有的代码都移动到一个web worker,只需要使用worker作为一个time事件源:

// 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链接的这个例子。

这个解决方案不适合如果你喜欢你的间隔工作在背景上,例如,播放音频或其他东西。但如果你感到困惑,例如当你回到页面或选项卡时,你的动画不能正常工作,这是一个很好的解决方案。

有很多方法可以达到这个目标,也许“webworker &;是最标准的当然,它不是最简单和方便的一个,特别是如果你没有足够的时间,所以你可以尝试这种方式:

基本概念:

  1. 为你的interval(或动画)建立一个名称,并设置你的interval(动画),所以它将在用户第一次打开你的页面时运行:var interval_id = setInterval(your_func, 3000);

  2. 通过$(window).focus(function() {});$(window).blur(function() {});,你可以clearInterval(interval_id)每次浏览器(选项卡)被停用和重新运行你的interval(动画)每次浏览器(选项卡)将再次通过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;
});

这是一个很老的问题,但我遇到了同样的问题。

如果你在chrome上运行你的网页,你可以阅读这篇文章Chrome 57的背景标签

基本上间隔计时器可以运行,如果它没有耗尽定时器预算。

预算的消耗基于计时器内任务的CPU时间使用情况。

基于我的场景,我将视频绘制到画布上并传输到WebRTC。

webrtc视频连接将保持更新,即使标签是不活跃的。

然而,你必须使用setInterval而不是requestAnimationFrame,但itt不建议用于UI渲染。

最好是监听visibilityChange事件并相应地改变呈现机制。

此外,你可以尝试Kaan Soral说,它应该根据文档工作。

我在这里为那些试图在计时器函数中解决这个问题的人带来了一个简单的解决方案,在@kbtzr在另一个答案中提到中,我们可以使用Date对象而不是固定的增量来计算从开始以来经过的时间,即使你离开了应用程序的选项卡,这也可以工作。

这是HTML示例。

<body>
<p id="time"></p>
</body>

然后这个JavaScript:

let display = document.querySelector('#time')
let interval
let time
function startTimer() {
let initialTime = new Date().getTime()
interval = setInterval(() => {
let now = new Date().getTime()
time = (now - initialTime) + 10
display.innerText = `${Math.floor((time / (60 * 1000)) % 60)}:${Math.floor((time / 1000) % 60)}:${Math.floor((time / 10) % 100)}`
}, 10)
}
startTimer()

这样,即使由于非活动选项卡的性能原因而增加了间隔值,所做的计算也将保证正确的时间。这是一个普通的代码,但我在我的React应用程序中使用了这种逻辑,你也可以在任何你需要的地方修改它。

有一个解决这个问题的方法,尽管实际上TAB必须在某些窗口中激活

  • 让你的非活动标签成为一个单独的浏览器窗口。
  • 不要创建任何其他窗口最大化(除非最大化窗口在你的窗口后面)。

这应该给浏览器一个始终处于活动状态的印象。

这有点麻烦,但也是一个快速的胜利。只要你能控制窗户的排列。

我修改了拉赛尔达的反应,添加了一个功能UI。

我添加了启动/恢复/暂停/停止操作。

const
timer = document.querySelector('.timer'),
timerDisplay = timer.querySelector('.timer-display'),
toggleAction = timer.querySelector('[data-action="toggle"]'),
stopAction = timer.querySelector('[data-action="stop"]'),
tickRate = 10;
let intervalId, initialTime, pauseTime = 0;


const now = () => new Date().getTime();


const formatTime = (hours, minutes, seconds) =>
[hours, minutes, seconds]
.map(v => `${isNaN(v) ? 0 : v}`.padStart(2, '0'))
.join(':');


const update = () => {
let
time = (now() - initialTime) + 10,
hours = Math.floor((time / (60000)) % 60),
minutes = Math.floor((time / 1000) % 60),
seconds = Math.floor((time / 10) % 100);
timerDisplay.textContent = formatTime(hours, minutes, seconds);
};


const
startTimer = () => {
initialTime = now();
intervalId = setInterval(update, tickRate);
},
resumeTimer = () => {
initialTime = now() - (pauseTime - initialTime);
intervalId = setInterval(update, tickRate);
},
pauseTimer = () => {
clearInterval(intervalId);
intervalId = null;
pauseTime = now();
},
stopTimer = () => {
clearInterval(intervalId);
intervalId = null;
initialTime = undefined;
pauseTime = 0;
},
restartTimer = () => {
stopTimer();
startTimer();
};


const setButtonState = (button, state, text) => {
button.dataset.state = state;
button.textContent = text;
};


const
handleToggle = (e) => {
switch (e.target.dataset.state) {
case 'pause':
setButtonState(e.target, 'resume', 'Resume');
pauseTimer();
break;
case 'resume':
setButtonState(e.target, 'pause', 'Pause');
resumeTimer();
break;
default:
setButtonState(e.target, 'pause', 'Pause');
restartTimer();
}
},
handleStop = (e) => {
stopTimer();
update();
setButtonState(toggleAction, 'initial', 'Start');
};


toggleAction.addEventListener('click', handleToggle);
stopAction.addEventListener('click', handleStop);
update();
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}


body {
display: flex;
justify-content: center;
align-items: center;
background: #000;
}


.timer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 0.5em;
}


.timer .timer-display {
font-family: monospace;
font-size: 3em;
background: #111;
color: #8F8;
border: thin solid #222;
padding: 0.25em;
}


.timer .timer-actions {
display: flex;
justify-content: center;
gap: 0.5em;
}


.timer .timer-actions button[data-action] {
font-family: monospace;
width: 6em;
border: thin solid #444;
background: #222;
color: #EEE;
padding: 0.5em;
cursor: pointer;
text-transform: uppercase;
}


.timer .timer-actions button[data-action]:hover {
background: #444;
border: thin solid #666;
color: #FFF;
}
<div class="timer">
<div class="timer-display"></div>
<div class="timer-actions">
<button data-action="toggle" data-state="initial">Start</button>
<button data-action="stop">Stop</button>
</div>
</div>