JavaScript 定时器在标签页失去焦点(例如切换到其他标签页或最小化浏览器)时,会被浏览器降低优先级或暂停,以节省资源。这会导致定时器不准确,甚至看起来停止了。要解决这个问题,你需要使用 requestAnimationFrame
或手动调整时间差。
1. 使用 requestAnimationFrame
(推荐)
requestAnimationFrame
是浏览器提供的 API,它会在浏览器下次重绘之前执行回调函数。这比 setTimeout
和 setInterval
更高效,也更能适应浏览器的节能机制。当标签页失去焦点时,requestAnimationFrame
会暂停,重新获得焦点时会继续执行。
let startTime = null;
let animationId;
function animate(timestamp) {
if (!startTime) startTime = timestamp;
const progress = timestamp - startTime;
// 执行你的动画或定时器逻辑,例如更新进度条
// ...
animationId = requestAnimationFrame(animate);
}
// 启动动画
animationId = requestAnimationFrame(animate);
// 可选:在标签页失去焦点时暂停动画,重新获得焦点时继续
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
startTime = null; // 重置起始时间
animationId = requestAnimationFrame(animate);
} else {
cancelAnimationFrame(animationId);
}
});
2. 手动调整时间差 (适用于需要精确计时的场景)
使用 setTimeout
或 setInterval
时,需要手动记录时间差并进行补偿。
let lastTime = performance.now();
let timerId;
function myTimer() {
const currentTime = performance.now();
const deltaTime = currentTime - lastTime; // 计算时间差
// 执行你的定时器逻辑,并根据 deltaTime 调整
// ...
lastTime = currentTime; // 更新 lastTime
timerId = setTimeout(myTimer, 1000 - deltaTime % 1000); // 调整下次执行的时间
}
// 启动定时器
timerId = setTimeout(myTimer, 1000);
// 可选:在标签页失去焦点时暂停定时器,重新获得焦点时继续
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
lastTime = performance.now(); // 更新 lastTime
timerId = setTimeout(myTimer, 1000);
} else {
clearTimeout(timerId);
}
});
选择哪种方法?
- 对于动画、平滑过渡等视觉效果,强烈推荐使用
requestAnimationFrame
。 - 对于需要精确计时的场景(例如倒计时、计时器),可以使用手动调整时间差的方法。但需要注意,即使这样也无法保证绝对的精度,因为浏览器可能会对后台标签页的定时器进行更 aggressive 的节流。
总结
以上两种方法都能有效解决标签页切换导致定时器停止的问题。选择哪种方法取决于你的具体需求。记住要处理 visibilitychange
事件,以便在标签页重新获得焦点时恢复定时器。
希望这些信息能帮到你!
标签:定时器,浏览器,setTimeout,标签,requestAnimationFrame,js,lastTime From: https://www.cnblogs.com/ai888/p/18583287