当 Chrome 中的选项卡处于非活动状态时,如何使 setInterval 也起作用?

发布于 2024-11-05 18:04:00 字数 615 浏览 0 评论 0原文

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

我猜这是设计使然,以便提高系统性能,但是有什么方法可以禁用这种行为吗?

在我的场景中这实际上是一个缺点。

I have a setInterval running a piece of code 30 times a second. This works great, however when I select another tab (so that the tab with my code becomes inactive), the setInterval is set to an idle state for some reason.

I made this simplified test case (http://jsfiddle.net/7f6DX/3/):

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

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

If you run this code and then switch to another tab, wait a few seconds and go back, the animation continues at the point it was when you switched to the other tab.

So the animation isn't running 30 times a second in case the tab is inactive. This can be confirmed by counting the amount of times the setInterval function is called each second - this will not be 30 but just 1 or 2 if the tab is inactive.

I guess that this is done by design so as to improve system performance, but is there any way to disable this behavior?

It’s actually a disadvantage in my scenario.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(18

灼疼热情 2024-11-12 18:04:00

在大多数浏览器上,非活动选项卡的执行优先级较低,这可能会影响 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 插件,它可以让您从 CSS 转换中受益就像 animate() 方法一样。

On most browsers inactive tabs have low priority execution and this can affect JavaScript timers.

If the values of your transition were calculated using real time elapsed between frames instead fixed increments on each interval, you not only workaround this issue but also can achieve a smother animation by using requestAnimationFrame as it can get up to 60fps if the processor isn't very busy.

Here's a vanilla JavaScript example of an animated property transition using 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>


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.

百善笑为先 2024-11-12 18:04:00

我在音频淡入淡出和 HTML5 播放器方面遇到了同样的问题。当选项卡变为非活动状态时,它被卡住了。
所以我发现 WebWorker 可以不受限制地使用间隔/超时。我用它来将“ticks”发布到主 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;
    }
});

I ran into the same problem with audio fading and HTML5 player. It got stuck when tab became inactive.
So I found out a WebWorker is allowed to use intervals/timeouts without limitation. I use it to post "ticks" to the main javascript.

WebWorkers Code:

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

Main 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;
    }
});
匿名的好友 2024-11-12 18:04:00

有一个使用 Web Workers 的解决方案(如前所述),因为它们在单独的进程中运行并且不会减慢

我编写了一个小脚本,可以在不更改代码的情况下使用它 - 它只是覆盖函数 setTimeout、clearTimeout,设置间隔、清除间隔。

只需将其包含在所有代码之前即可。

更多信息

There is a solution to use Web Workers (as mentioned before), because they run in separate process and are not slowed down

I've written a tiny script that can be used without changes to your code - it simply overrides functions setTimeout, clearTimeout, setInterval, clearInterval.

Just include it before all your code.

more info here

黑白记忆 2024-11-12 18:04:00

当选项卡处于非活动状态或工作但不在正确的时间段时,setIntervalrequestAnimationFrame 都不起作用。解决方案是使用另一个时间事件源。例如,网络套接字网络工作人员是两个事件源,在选项卡处于非活动状态时可以正常工作。因此,无需将所有代码移至 Web Worker,只需使用 Worker 作为时间事件源:

// 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 链接

Both setInterval and requestAnimationFrame don't work when tab is inactive or work but not at the right periods. A solution is to use another source for time events. For example web sockets or web workers are two event sources that work fine while tab is inactive. So no need to move all of your code to a web worker, just use worker as a time event source:

// 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 link of this sample.

↙厌世 2024-11-12 18:04:00

对我来说,像这里的其他人一样在后台播放音频并不重要,我的问题是我有一些动画,当您在其他选项卡中并返回它们时,它们表现得很疯狂。我的解决方案是将这些动画放入if中,以防止非活动选项卡

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

由于我的动画在选项卡处于活动状态时才运行。
我希望这对处理我的案件的人有所帮助。

For me it's not important to play audio in the background like for others here, my problem was that I had some animations and they acted like crazy when you were in other tabs and coming back to them. My solution was putting these animations inside if that is preventing inactive tab:

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

thanks to that my animation was running only if tab was active.
I hope this will help someone with my case.

↙厌世 2024-11-12 18:04:00

只需执行以下操作:

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 毫秒)。

Just do this:

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

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

Inactive browser tabs buffer some of the setInterval or setTimeout functions.

stop(true,true) will stop all buffered events and execute immediatly only the last animation.

The window.setTimeout() method now clamps to send no more than one timeout per second in inactive tabs. In addition, it now clamps nested timeouts to the smallest value allowed by the HTML5 specification: 4 ms (instead of the 10 ms it used to clamp to).

淑女气质 2024-11-12 18:04:00

受到 Ruslan 答案的启发,您可以将一个方便的小类复制并粘贴到代码中作为 setInterval 的替代品:

class WorkerInterval {
  worker = null;
  constructor(callback, interval) {
    const blob = new Blob([`setInterval(() => postMessage(0), ${interval});`]);
    const workerScript = URL.createObjectURL(blob);
    this.worker = new Worker(workerScript);
    this.worker.onmessage = callback;
  }

  stop() {
    this.worker.terminate();
  }
}

示例用法:

   const interval = new WorkerInterval(callback, 100);
   // sometime later
   interval.stop();

Inspired by Ruslan's answer, a handy little class you can just copy&paste into your code as a replacement for setInterval:

class WorkerInterval {
  worker = null;
  constructor(callback, interval) {
    const blob = new Blob([`setInterval(() => postMessage(0), ${interval});`]);
    const workerScript = URL.createObjectURL(blob);
    this.worker = new Worker(workerScript);
    this.worker.onmessage = callback;
  }

  stop() {
    this.worker.terminate();
  }
}

Example usage:

   const interval = new WorkerInterval(callback, 100);
   // sometime later
   interval.stop();
我不是你的备胎 2024-11-12 18:04:00

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

我正在做一个简单的这里的事情:

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

如果您停留在页面上,它就会按预期工作。
但如果你隐藏该选项卡几秒钟,当你回来时你会看到一个奇怪的东西。

就好像所有在您不活动期间未发生的事件现在都会一次性发生。所以在几秒钟内你会收到类似 X 的事件。它们速度非常快,可以同时看到所有 6 个跨度。

因此,它接缝 chrome 只会延迟事件,因此当您回来时,所有事件都会发生,但会同时发生......

这是一个简单的幻灯片放映的实际应用程序。想象一下数字是图像,如果用户回来时保持隐藏的选项卡,他将看到所有图像都浮动,完全混乱。

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

I think that a best understanding about this problem is in this example: http://jsfiddle.net/TAHDb/

I am doing a simple thing here:

Have a interval of 1 sec and each time hide the first span and move it to last, and show the 2nd span.

If you stay on page it works as it is supposed.
But if you hide the tab for some seconds, when you get back you will see a weired thing.

Its like all events that didn't ucur during the time you were inactive now will ocur all in 1 time. so for some few seconds you will get like X events. they are so quick that its possible to see all 6 spans at once.

So it seams chrome only delays the events, so when you get back all events will occur but all at once...

A pratical application were this ocur iss for a simple slideshow. Imagine the numbers being Images, and if user stay with tab hidden when he came back he will see all imgs floating, Totally mesed.

To fix this use the stop(true,true) like pimvdb told.
THis will clear the event queue.

咽泪装欢 2024-11-12 18:04:00

我为任何试图在计时器函数中解决此问题的人带来了一个简单的解决方案,正如 @kbtzr 在另一个答案中提到的< /a> 我们可以使用 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 应用程序中使用了这个逻辑,您也可以根据需要修改它。

I bring here a simple solution for anyone who is trying to get around this problem in a timer function, where as @kbtzr mentioned in another answer we can use the Date object instead of fixed increments to calculate the time that has passed since the beginning, which will work even if you are out from the application's tab.

This is the example HTML.

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

Then this 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()

That way, even if the interval value is increased for performance reasons of inactive tabs, the calculation made will guarantee the correct time. This is a vanilla code, but I used this logic in my React application, and you can modify it for wherever you need as well.

优雅的叶子 2024-11-12 18:04:00

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

Heavily influenced by Ruslan Tushov's library, I've created my own small library. Just add the script in the <head> and it will patch setInterval and setTimeout with ones that use WebWorker.

月光色 2024-11-12 18:04:00

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

对我来说,这是最简单且干扰最小的解决方案 - 除了播放微弱/几乎空洞的声音之外,没有其他潜在的副作用

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

(从其他答案中,我看到有些人使用不同的属性音频标签,我确实想知道是否可以使用音频标签来获得完整的性能,而无需实际播放某些内容)

Playing an audio file ensures full background Javascript performance for the time being

For me, it was the simplest and least intrusive solution - apart from playing a faint / almost-empty sound, there are no other potential side effects

You can find the details here: https://stackoverflow.com/a/51191818/914546

(From other answers, I see that some people use different properties of the Audio tag, I do wonder whether it's possible to use the Audio tag for full performance, without actually playing something)

做个ˇ局外人 2024-11-12 18:04:00

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

如果您在 Chrome 上运行网站,可以阅读这篇文章 Chrome 中的后台标签57.

基本上,如果间隔计时器没有超出计时器预算,它就可以运行。

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

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

即使选项卡处于非活动状态,webrtc 视频连接也会不断更新。

但是,您必须使用 setInterval 而不是 requestAnimationFrame,但不建议将其用于 UI 渲染。

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

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

It is quite old question but I encountered the same issue.

If you run your web on chrome, you could read through this post Background Tabs in Chrome 57.

Basically the interval timer could run if it haven't run out of the timer budget.

The consumption of budget is based on CPU time usage of the task inside timer.

Based on my scenario, I draw video to canvas and transport to WebRTC.

The webrtc video connection would keep updating even the tab is inactive.

However you have to use setInterval instead of requestAnimationFrame but itt is not recommended for UI rendering though.

It would be better to listen visibilityChange event and change render mechenism accordingly.

Besides, you could try what Kaan Soral suggests and it should works based on the documentation.

书信已泛黄 2024-11-12 18:04:00

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

I modified Lacerda's response, by adding a functioning UI.

I added start/resume/pause/stop actions.

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>

淡墨 2024-11-12 18:04:00

这是 setInterval() 和 setTimeout() 的替代品,它在非活动选项卡中工作: https:// www.npmjs.com/package/worker-timers

对于依赖 WindowTimers(如 setInterval() 或 setTimeout())的脚本,当运行脚本的站点失去焦点时,事情会变得混乱。在这种情况下,Chrome、Firefox 以及其他浏览器可能会将调用这些计时器的频率限制为每秒最多一次。

但这仅适用于主线程,不会影响 Web Workers 的行为。因此,可以通过使用worker进行实际调度来避免节流。这正是工人计时器所做的。

Here is a replacement for setInterval() and setTimeout() which works in inactive tabs: https://www.npmjs.com/package/worker-timers.

For scripts that rely on WindowTimers like setInterval() or setTimeout() things get confusing when the site which the script is running on loses focus. Chrome, Firefox and maybe others throttle the frequency at which they invoke those timers to a maximum of once per second in such a situation.

However this is only true for the main thread and does not affect the behavior of Web Workers. Therefore it is possible to avoid the throttling by using a worker to do the actual scheduling. This is exactly what worker-timers does.

暮年 2024-11-12 18:04:00

注意:如果您希望间隔在后台运行(例如播放音频或其他内容),则此解决方案不适合。但是,如果您感到困惑,例如返回页面或选项卡时动画无法正常工作,那么这是一个很好的解决方案。

实现这一目标的方法有很多,也许“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;
});

Note: This solution is not suitable if you like your interval works on the background, for example, playing audio or something else. But if you are confused for example about your animation not working properly when coming back to your page or tab, this is a good solution.

There are many ways to achieve this goal, maybe the "WebWorkers" is the most standard one but certainly, it's not the easiest and handy one, especially If you don't have enough Time, so you can try this way:

The basic concept:

  1. build a name for your interval(or animation) and set your interval(animation), so it would run when user first time open your page : var interval_id = setInterval(your_func, 3000);

  2. by $(window).focus(function() {}); and $(window).blur(function() {}); you can clearInterval(interval_id) everytime browser(tab) is deactived and ReRun your interval(animation) everytime browser(tab) would acive again by interval_id = setInterval();

Sample code:

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;
});
怼怹恏 2024-11-12 18:04:00

这是我的粗略解决方案

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

Here's my rough solution

(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;
})();
扶醉桌前 2024-11-12 18:04:00

我能够使用音频标签并处理其 ontimeupdate 事件在至少 250 毫秒内调用我的回调函数。每秒调用 3-4 次。比滞后一秒的 setTimeout 更好

I was able to call my callback function at minimum of 250ms using audio tag and handling its ontimeupdate event. Its called 3-4 times in a second. Its better than one second lagging setTimeout

甜中书 2024-11-12 18:04:00

尽管实际上选项卡必须在某些窗口中处于活动状态,但此问题有一个解决方法。

  • 将非活动选项卡设为单独的浏览器窗口。
  • 不要使任何其他窗口最大化(除非最大化的窗口位于您的窗口后面)。

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

这有点麻烦,但也是一个快速的胜利。前提是可以控制窗口的排列。

There is a workaround for this problem, although actually the tab must be made active in some window.

  • Make your inactive tab a separate browser window.
  • Don't make any other window maximized (unless the maximized window is behind yours).

This should give the browser the impression of always being active.

This is bit cumbersome, but also a quick win. Provided one has control over windows arrangement.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文