出处:掘金
原作者:前端微白
什么是用户无操作?
指用户在特定时间内未进行任何操作(如鼠标移动、键盘输入、点击等)。该技术在众多场景中有广泛应用:
- 安全系统:用户长时间无操作自动退出登录
- 媒体应用:暂停播放视频或音乐节省资源
- 数据保护:敏感信息自动隐藏
- 分析工具:用户活跃度统计
- 节能模式:页面切换至低功耗显示
实现原理:事件监听与定时器管理
核心是通过监听用户行为事件并重置计时器来实现:
graph TB A(开始) --> B(初始化空闲计时器) B --> C(监听用户交互事件) C --> D{事件发生?} D --是--> E(重置空闲计时器) E --> B D --否--> F{超时?} F --否--> D F --是--> G(触发空闲状态回调)
基础实现方案
class IdleDetector {
constructor(timeout = 300000, onIdle = () => {}) {
this.timeout = timeout; // 默认 5 分钟
this.onIdle = onIdle; // 空闲状态回调函数
this.timer = null; // 定时器实例
this.events = ['mousemove', 'keydown', 'mousedown', 'touchstart', 'scroll'];
// 绑定 this 上下文
this.resetTimer = this.resetTimer.bind(this);
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
}
// 重置空闲定时器
resetTimer() {
if (this.timer) clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.onIdle();
}, this.timeout);
}
// 开始检测
start() {
// 初始启动定时器
this.resetTimer();
// 添加事件监听
this.events.forEach(event => {
document.addEventListener(event, this.resetTimer);
});
}
// 停止检测
stop() {
if (this.timer) clearTimeout(this.timer);
this.events.forEach(event => {
document.removeEventListener(event, this.resetTimer);
});
}
}
使用示例:
const detector = new IdleDetector(60000, () => {
console.log('用户已空闲超过60秒');
// 执行安全退出或暂停操作
});
// 开始检测
detector.start();
// 当需要停止检测时
// detector.stop();
页面可见性优化
当用户切换标签页或最小化浏览器时,应调整监测逻辑:
class AdvancedIdleDetector extends IdleDetector {
constructor(timeout, onIdle) {
super(timeout, onIdle);
this.handleVisibilityChange = this.handleVisibilityChange.bind(this);
}
// 页面可见性变更处理
handleVisibilityChange() {
if (document.visibilityState === 'hidden') {
// 页面不可见时暂停检测
this.stop();
} else {
// 页面恢复可见时重新开始检测
this.resetTimer();
}
}
start() {
super.start();
// 添加页面可见性监听
document.addEventListener('visibilitychange', this.handleVisibilityChange);
}
stop() {
super.stop();
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
}
}
多层级回调系统
实现不同级别的空闲提醒:
class MultiLevelIdleDetector extends IdleDetector {
constructor(timeout, callbacks = {}) {
super(timeout, callbacks.onIdle || (() => {}));
this.callbacks = callbacks;
this.warningLevels = [];
// 初始化警告级别计时器
if (callbacks.onWarning) {
// 设置警告级别(如主空闲时间前 30 秒)
this.warningLevels.push({
time: timeout - 30000,
callback: callbacks.onWarning
});
}
this.activeTimers = [];
}
resetTimer() {
super.resetTimer();
// 清除所有警告计时器
this.activeTimers.forEach(timer => clearTimeout(timer));
this.activeTimers = [];
// 设置新的警告计时器
this.warningLevels.forEach(level => {
this.activeTimers.push(setTimeout(
level.callback,
level.time
));
});
}
}
性能优化与节流技术
避免事件触发过于频繁导致的性能问题:
class OptimizedIdleDetector extends IdleDetector {
constructor(timeout, onIdle) {
super(timeout, onIdle);
this.lastEventTime = 0;
this.throttleInterval = 1000; // 1 秒节流
}
// 节流版本的事件处理器
handleEvent = throttle(() => {
this.resetTimer();
}, this.throttleInterval);
start() {
// 使用节流处理的事件监听
this.events.forEach(event => {
document.addEventListener(event, this.handleEvent);
});
this.resetTimer();
}
}
// 节流函数实现
function throttle(fn, interval) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= interval) {
lastCall = now;
return fn.apply(this, args);
}
};
}
实际应用场景
安全登出系统
const logoutDetector = new AdvancedIdleDetector(300000, () => {
// 显示倒计时弹窗
showModal(
'安全提醒',
'您即将因长时间无操作被登出',
{ onConfirm: logout }
);
});
function showModal(title, message, options) {
// 创建模态框逻辑
const modal = document.createElement('div');
modal.innerHTML = `
<div class="modal-content">
<h3>${title}</h3>
<p>${message}</p>
<div class="countdown">60秒</div>
<button id="stayLoggedIn">保持登录</button>
</div>
`;
// 添加按钮事件
modal.querySelector('#stayLoggedIn').addEventListener('click', () => {
logoutDetector.resetTimer();
document.body.removeChild(modal);
});
document.body.appendChild(modal);
// 倒计时功能
let seconds = 60;
const countdownEl = modal.querySelector('.countdown');
const countdownTimer = setInterval(() => {
seconds--;
countdownEl.textContent = `${seconds}秒`;
if (seconds <= 0) {
clearInterval(countdownTimer);
options.onConfirm();
}
}, 1000);
}
function logout() {
// 登出逻辑
console.log("用户已从系统登出");
}
媒体播放控制
class MediaPlayer {
constructor(videoElement) {
this.video = videoElement;
this.idleDetector = new OptimizedIdleDetector(120000, () => {
this.pause();
this.showPlayIcon();
});
this.video.addEventListener('play', () => {
this.idleDetector.start();
});
this.video.addEventListener('pause', () => {
this.idleDetector.stop();
});
}
showPlayIcon() {
const playIcon = document.createElement('div');
playIcon.className = 'play-overlay';
playIcon.innerHTML = '▶';
playIcon.addEventListener('click', () => {
this.play();
playIcon.remove();
});
this.video.parentElement.appendChild(playIcon);
}
play() {
this.video.play();
}
pause() {
this.video.pause();
}
}
性能优化与最佳实践
// 事件委托优化:在根元素上监听事件,避免多个监听器
document.documentElement.addEventListener('mousemove', handleEvent);
// 根据需求选择必要事件,避免过度监听
const essentialEvents = ['keydown', 'mousedown'];
// 页面加载优化:延迟初始化避免阻塞页面加载
window.addEventListener('load', () => {
const detector = new IdleDetector();
detector.start();
});
// 根据不同场景定义不同空闲行为
const config = {
dashboard: {
timeout: 300000,
callback: logout
},
videoPlayer: {
timeout: 60000,
callback: pauseVideo
}
};
注意事项
- 用户体验优先:空闲处理不应该干扰用户操作
- 隐私保护:避免检测涉及隐私的行为
- 性能平衡:事件监听需考虑性能影响
- 可访问性设计:确保不干扰辅助工具使用
- 弹性设计:提供配置选项供不同场景使用