我有一个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。

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

这在我看来是劣势。


当前回答

我在使用HTML5播放器时也遇到了同样的问题。当标签变成非活动时,它被卡住了。 所以我发现WebWorker允许使用没有限制的间隔/超时。我用它来张贴“tick”到主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;
    }
});

其他回答

在大多数浏览器上,不活跃的选项卡执行优先级低,这可能会影响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>


对于后台任务(与ui无关)

@UpTheCreek评论:

表现问题没问题,但还是 有些事情你需要继续做下去。

如果你有后台任务需要在给定的时间间隔内精确执行,你可以使用HTML5 Web Workers。看看下面Möhre的回答,了解更多细节…

CSS vs JS的“动画”

这个问题和其他许多问题都可以通过使用CSS转换/动画来避免,而不是基于JavaScript的动画,这会增加相当大的开销。我推荐这个jQuery插件,它可以让你像animate()方法一样从CSS过渡中受益。

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

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

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

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

我为那些试图在计时器函数中解决这个问题的人带来了一个简单的解决方案,正如@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应用程序中使用了这种逻辑,你也可以在任何你需要的地方修改它。

我修改了Lacerda的回应,添加了一个功能正常的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>

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

有很多方法可以实现这个目标,也许“WebWorkers”是最标准的一个,但当然,它不是最简单和方便的一个,特别是如果你没有足够的时间,所以你可以试试这种方法:

基本概念:

为你的间隔(或动画)建立一个名字,并设置你的间隔(动画),所以它会运行时,用户第一次打开你的页面:var interval_id = setInterval(your_func, 3000); 通过$(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;
});