1. 背景

最近开发项目时,常碰到“用户在一定时间内无任何操作时,跳转到某个页面”的需求。

网上冲浪后,也没有找到一个比较好的js封装去解决这个问题,从而决定自己实现。

2. 如何判断页面是否空闲

首先,我们要知道什么是空闲?用户一定时间内,没有对网页进行任何操作,则当前网页为空闲状态。

用户操作网页,无非就是通过鼠标键盘两个输入设备(暂不考虑手柄等设备)。因而我们可以监听相应的输入事件,来判断网页是否空闲(用户是否有操作网页)。

  1. 监听鼠标移动事件mousemove
  2. 监听键盘/鼠标按下事件keydown
  3. 在用户进入网页后,设置延时跳转,如果触发以上事件,则移除延时器,并重新开始。

3. 网页空闲检测实现

3.1 简易实现

以下代码,简单实现了一个判断网页空闲的方法:

 1const onIdleDetection = (callback, timeout = 15, immediate = false) => {
 2  let pageTimer;
 3  
 4  const onClearTimer = () => {
 5    pageTimer && clearTimeout(pageTimer);
 6    pageTimer = undefined;
 7  };
 8  const onStartTimer = () => {
 9    onClearTimer();
10    pageTimer = setTimeout(() => {
11      callback();
12    }, timeout * 1000);
13  };
14
15  const startDetection = () => {
16    onStartTimer();
17    document.addEventListener('keydown', onStartTimer);
18    document.addEventListener('mousemove', onStartTimer);
19  };
20  const stopDetection = () => {
21    onClearTimer();
22    document.removeEventListener('keydown', onStartTimer);
23    document.removeEventListener('mousemove', onStartTimer);
24  };
25  const restartDetection = () => {
26      onClearTimer();
27      onStartTimer();
28  };
29
30  if (immediate) {
31    startDetection();
32  }
33
34  return {
35    startDetection,
36    stopDetection,
37    restartDetection
38  };
39};

也许你注意到了,我并没有针对onStartTimer事件进行防抖,那这是不是会对性能有影响呢?

是的,肯定有那么点影响,那我为啥不添加防抖呢?

这是因为添加防抖后,形成了setTimeout嵌套,嵌套setTimeout会有精度问题(参考)。

或许你还会说,非活动标签页(网页被隐藏)的setTimeout的执行和精度会有问题(参考非活动标签的超时)。

确实存在以上问题,接下来我们就来一一解决吧!

3.2 处理频繁触发问题

我们可以通过添加一个变量记录开始执行时间,当下一次执行与当前的时间间隔小于某个值时直接退出函数,从而解决这个问题(节流思想应用)。

 1const onIdleDetection = (callback, timeout = 15, immediate = false) => {
 2  let pageTimer;
 3  
 4  let beginTime = 0;
 5  const onStartTimer = () => {
 6    
 7    const currentTime = Date.now();
 8    if (pageTimer && currentTime - beginTime < 100) {
 9      return;
10    }
11
12    onClearTimer();
13    
14    beginTime = currentTime;
15    pageTimer = setTimeout(() => {
16      callback();
17    }, timeout * 1000);
18  };
19  const onClearTimer = () => {
20    pageTimer && clearTimeout(pageTimer);
21    pageTimer = undefined;
22  };
23  
24  const startDetection = () => {
25    onStartTimer();
26    document.addEventListener('keydown', onStartTimer);
27    document.addEventListener('mousemove', onStartTimer);
28  };
29  const stopDetection = () => {
30    onClearTimer();
31    document.removeEventListener('keydown', onStartTimer);
32    document.removeEventListener('mousemove', onStartTimer);
33  };
34  const restartDetection = () => {
35      onClearTimer();
36      onStartTimer();
37  };
38  
39  if (immediate) {
40    startDetection();
41  }
42
43  return {
44    startDetection,
45    stopDetection,
46    restartDetection
47  };
48};

3.3 处理页面被隐藏的情况(完整实现)

我们可以监听visibilitychange事件,在页面隐藏时移除延时器,然后页面显示时继续计时,从而解决这个问题。

 1
 2 * 网页空闲检测
 3 * @param {() => void} callback 空闲时执行即一定时长无操作时触发
 4 * @param {number} [timeout=15] 时长默认15s单位
 5 * @param {boolean} [immediate=false] 是否立即开始默认 false
 6 * @returns
 7 */
 8const onIdleDetection = (callback, timeout = 15, immediate = false) => {
 9  let pageTimer;
10  let beginTime = 0;
11  const onClearTimer = () => {
12    pageTimer && clearTimeout(pageTimer);
13    pageTimer = undefined;
14  };
15  const onStartTimer = () => {
16    const currentTime = Date.now();
17    if (pageTimer && currentTime - beginTime < 100) {
18      return;
19    }
20
21    onClearTimer();
22    beginTime = currentTime;
23    pageTimer = setTimeout(() => {
24      callback();
25    }, timeout * 1000);
26  };
27
28  const onPageVisibility = () => {
29     
30     onClearTimer();
31
32     if (document.visibilityState === 'visible') {
33       const currentTime = Date.now();
34       
35       if (currentTime - beginTime >= timeout * 1000) {
36         callback();
37         return;
38       }
39       
40       pageTimer = setTimeout(() => {
41         callback();
42       }, timeout * 1000 - (currentTime - beginTime));
43     }
44  };
45
46  const startDetection = () => {
47    onStartTimer();
48    document.addEventListener('keydown', onStartTimer);
49    document.addEventListener('mousemove', onStartTimer);
50    document.addEventListener('visibilitychange', onPageVisibility);
51  };
52
53  const stopDetection = () => {
54    onClearTimer();
55    document.removeEventListener('keydown', onStartTimer);
56    document.removeEventListener('mousemove', onStartTimer);
57    document.removeEventListener('visibilitychange', onPageVisibility);
58  };
59  
60  const restartDetection = () => {
61      onClearTimer();
62      onStartTimer();
63  };
64
65  if (immediate) {
66    startDetection();
67  }
68
69  return {
70    startDetection,
71    stopDetection,
72    restartDetection
73  };
74};

通过以上代码,我们就完整地实现了一个网页空闲状态检测的方法。

4. 扩展阅读

chrome浏览器其实提供了一个Idle DetectionAPI,来实现网页空闲状态的检测,但是这个API还是一个实验性特性,并且Firefox与Safari不支持。API参考

个人笔记记录 2021 ~ 2025