前端记录页面停留时长
主要逻辑
1.进入页面开始计时
2.如果浏览器tab切换,停止计时,重新切换回当前页面,继续累加计时
3.如果浏览器缩小或被遮盖,停止计时,重新切换回当前页面,继续累加计时
4.如果浏览器刷新,重新计时
5.如果浏览器关闭,停止计时
代码实现
基于以上逻辑,使用以下代码进行实现:
let startTime;
let elapsedTime = 0;
let timerInterval;
let isTimerRunning = false;
function startTimer() {
if (!isTimerRunning) {
startTime = new Date().getTime() / 1000 - elapsedTime;
timerInterval = setInterval(updateTimer, 1000);
isTimerRunning = true;
}
}
function stopTimer() {
if (isTimerRunning) {
clearInterval(timerInterval);
elapsedTime = new Date().getTime() / 1000 - startTime;
isTimerRunning = false;
}
}
function updateTimer() {
const currentTime = new Date().getTime() / 1000;
elapsedTime = currentTime - startTime;
displayTime(elapsedTime);
}
function displayTime(time) {
const timeString = `${Math.floor(time)}秒`;
console.log('timeString', timeString);
}
// 监听页面可见性变化
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
stopTimer();
} else {
startTimer();
}
});
// 页面加载完成后开始计时
window.addEventListener('load', startTimer);
// 监听页面关闭或刷新
window.addEventListener('beforeunload', stopTimer);
经过测试,发现一个特殊情况:当缩小浏览器或者切换tab到其他页面(其他电脑应用)后,经过十几分钟(或者更长时间)后再重新回到当前计时页面,以上代码会出现一个时间偏差,例如原本离开的时间是6秒,再回来应该从第7秒开始累加,但回来后会从10秒或者20秒开始累加;
原因如下:
js执行的任务队列分别有微任务和宏任务两种,而setInterval 和setTimeout 属于宏任务,在浏览器页面的执行机制里,当页面失焦被置于后台时,会因为浏览器的性能机制导致被延迟执行,具体延迟执行时长是不确定的(取决于当前页面的代码逻辑和浏览器本身的调度):
根据以上的情况和原因说明,不使用定时器,改用requestAnimationFrame 方法来实现更精准的时长计算
为什么使用requestAnimationFrame 可以解决时长精准度的问题?
可以概括为:requestAnimationFrame与浏览器的渲染周期同步,这意味着它以与浏览器的渲染帧速率一致的一致速率(通常为每秒 60 次)调用。这种同步有助于减少时间测量的差异。且当浏览器被缩小,或切换tab,该事件会被暂停调用,重新激活页面时会再次以每秒60次的频率调用
官方说明:
以下是修改之后的代码:
let startTime = 0;
let pauseTime = 0;
let accumulatedTime = 0;
let timerId = null;
let isRunning = false;
function startTimer() {
if (!isRunning) {
startTime = performance.now();
isRunning = true;
requestAnimationFrame(updateTimer);
}
}
function updateTimer(timestamp) {
if (isRunning) {
const currentTime = timestamp;
const elapsed = Math.floor(currentTime - startTime + accumulatedTime);
// 更新计时器的显示
console.log(`Elapsed time: ${elapsed}ms:::`,elapsed/1000);
timerId = requestAnimationFrame(updateTimer);
}
}
function pauseTimer() {
if (isRunning) {
pauseTime = performance.now();
accumulatedTime += pauseTime - startTime;
isRunning = false;
cancelAnimationFrame(timerId);
}
}
// 监听浏览器的 visibilitychange 事件
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
pauseTimer();
} else {
startTimer();
}
});
// 初始化计时器
startTimer();
在html里引用以上修改后的代码,未出现离开时间较长时间偏差的问题,特此记录。
requestAnimationFrame的浏览器兼容性(2024/7/3):
参考文档:
动画演示js的EventLoop:https://juejin.cn/post/6969028296893792286
requestAnimationFrame的使用场景举例:https://juejin.cn/post/7190728064458817591#heading-1
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!