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

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

这在我看来是劣势。


当前回答

我修改了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>

其他回答

我在使用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;
    }
});

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

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

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

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

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

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

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

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

此外,您可以尝试Kaan Soral建议的方法,它应该可以根据文档工作。

这是我的粗略解决方案

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

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

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

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

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

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

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

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

要解决这个问题,请像pimvdb告诉的那样使用stop(true,true)。 这将清除事件队列。

setInterval和requestAnimationFrame都不工作时,选项卡是不活跃的或工作,但不是在正确的时间。一种解决方案是使用另一个时间事件源。例如,web套接字或web工作者是两个事件源,当选项卡是非活动的时候工作得很好。所以不需要把你所有的代码都移动到一个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链接。