如何在JavaScript中检测空闲时间

有没有可能检测到& 闲置"时间在JavaScript?

我的主要用例可能是预取或预加载内容。

我将空闲时间定义为用户不活动或没有任何CPU使用的一段时间

451169 次浏览

有没有可能让一个函数每10秒运行一次,并检查一个“计数器”?变量?如果可能的话,你可以在页面上进行鼠标悬停,不是吗?

如果是,使用鼠标悬停事件重置“计数器”;变量。如果函数被调用,并且计数器在您预先确定的范围之上,那么执行您的操作。

您可能可以通过检测窗体主体上的鼠标移动并使用最后的移动时间更新全局变量来拼凑一些东西。然后,您需要运行一个间隔计时器,定期检查最后一次移动时间,如果距离检测到最后一次鼠标移动已经足够长,则执行一些操作。

您可以在文档主体上附加一个单击或鼠标移动事件,以重置计时器。

有一个函数,你在定时间隔调用,检查定时器是否超过指定的时间(如1000毫秒),并开始你的预加载。

下面是tvanfosson的想法的粗略jQuery实现:

$(document).ready(function(){


idleTime = 0;


//Increment the idle time counter every second.
var idleInterval = setInterval(timerIncrement, 1000);
   

function timerIncrement()
{
idleTime++;
if (idleTime > 2)
{
doPreload();
}
}
   

//Zero the idle timer on mouse movement.
$(this).mousemove(function(e){
idleTime = 0;
});
   

function doPreload()
{
//Preload images, etc.
}
   

})

你可以使用上面列出的鼠标移动技巧来检测网页上的不活跃状态,但这并不能告诉你用户不在另一个窗口或选项卡的另一个页面上,或者用户在Photoshop中,只是此时没有在看你的页面。

一般来说,我只会预取,并依赖于客户端的多任务处理。如果您真的需要这个功能,您可以在Windows中使用ActiveX控件,但它充其量是丑陋的。

JavaScript没有办法告诉CPU的使用情况。这将打破运行在沙盒中的JavaScript。

除此之外,连接页面的onmouseover和onkeydown事件可能会工作。

你也可以在onload事件中使用setTimeout来调度延迟后调用的函数。

// Call aFunction after 1 second
window.setTimeout(aFunction, 1000);

类似于Peter J的解决方案(使用jQuery自定义事件)…

// Use the jquery-idle-detect.js script below
$(window).on('idle:start', function() {
// Start your prefetch, etc. here...
});


$(window).on('idle:stop', function() {
// Stop your prefetch, etc. here...
});

文件# EYZ0

(function($, $w) {
// Expose configuration option
// Idle is triggered when no events for 2 seconds
$.idleTimeout = 2000;


// Currently in idle state
var idle = false;


// Handle to idle timer for detection
var idleTimer = null;


// Start the idle timer and bind events on load (not DOM-ready)
$w.on('load', function() {
startIdleTimer();
$w.on('focus resize mousemove keyup', startIdleTimer)
.on('blur', idleStart) // Force idle when in a different tab/window
;
]);


function startIdleTimer() {
clearTimeout(idleTimer); // Clear prior timer


if (idle) $w.trigger('idle:stop'); // If idle, send stop event
idle = false; // Not idle


var timeout = ~~$.idleTimeout; // Option to integer
if (timeout <= 100)
timeout = 100; // Minimum 100 ms
if (timeout > 300000)
timeout = 300000; // Maximum 5 minutes


idleTimer = setTimeout(idleStart, timeout); // New timer
}


function idleStart() {
if (!idle)
$w.trigger('idle:start');
idle = true;
}


}(window.jQuery, window.jQuery(window)))
这是一个使用jQuery处理鼠标移动和按键事件的简单脚本。

.

.

.
<script type="text/javascript">
var idleTime = 0;
$(document).ready(function () {
// Increment the idle time counter every minute.
var idleInterval = setInterval(timerIncrement, 60000); // 1 minute


// Zero the idle timer on mouse movement.
$(this).mousemove(function (e) {
idleTime = 0;
});
$(this).keypress(function (e) {
idleTime = 0;
});
});


function timerIncrement() {
idleTime = idleTime + 1;
if (idleTime > 19) { // 20 minutes
window.location.reload();
}
}
</script>

使用普通JavaScript:

var inactivityTime = function () {
var time;
window.onload = resetTimer;
// DOM Events
document.onmousemove = resetTimer;
document.onkeydown = resetTimer;


function logout() {
alert("You are now logged out.")
//location.href = 'logout.html'
}


function resetTimer() {
clearTimeout(time);
time = setTimeout(logout, 3000)
// 1000 milliseconds = 1 second
}
};

并在需要的地方初始化函数(例如:onPageLoad)。

window.onload = function() {
inactivityTime();
}

如果需要,您可以添加更多DOM事件。最常用的有:

document.onload = resetTimer;
document.onmousemove = resetTimer;
document.onmousedown = resetTimer; // touchscreen presses
document.ontouchstart = resetTimer;
document.onclick = resetTimer;     // touchpad clicks
document.onkeydown = resetTimer;   // onkeypress is deprectaed
document.addEventListener('scroll', resetTimer, true); // improved; see comments

或者使用数组注册所需的事件

window.addEventListener('load', resetTimer, true);
var events = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart'];
events.forEach(function(name) {
document.addEventListener(name, resetTimer, true);
});

DOM事件列表:http://www.w3schools.com/jsref/dom_obj_event.asp

记住根据需要使用windowdocument。这里你可以看到它们之间的区别: JavaScript中窗口、屏幕和文档的区别是什么?< / >

代码更新带@frank-conijn和@daxchen改进:如果滚动是在可滚动元素内,window.onscroll将不会触发,因为滚动事件不会冒泡。在window.addEventListener('scroll', resetTimer, true)中,第三个参数告诉侦听器在捕获阶段而不是冒泡阶段捕获事件。

我创建了一个小的库来做这个:

https://github.com/shawnmclean/Idle.js

描述:

微小的JavaScript库报告用户在浏览器中的活动 (离开,空闲,没有看网页,在不同的标签,等)。这是独立的 其他JavaScript库,如jQuery.

Visual Studio用户可以从NuGet通过:

Install-Package Idle.js

对于其他有同样问题的用户。这是我刚编的一个函数。

它在用户每次鼠标移动时运行,或者在每次鼠标移动时清除一个计时器。

<script>
// Timeout in seconds
var timeout = 10; // 10 seconds


// You don't have to change anything below this line, except maybe
// the alert('Welcome back!') :-)
// ----------------------------------------------------------------
var pos = '', prevpos = '', timer = 0, interval = timeout / 5 * 1000;
timeout = timeout * 1000 - interval;


function mouseHasMoved(e){
document.onmousemove = null;
prevpos = pos;
pos = e.pageX + '+' + e.pageY;
if(timer > timeout){
timer = 0;
alert('Welcome back!');
}
}


setInterval(function(){
if(pos == prevpos){
timer += interval;
}else{
timer = 0;
prevpos = pos;
}
document.onmousemove = function(e){
mouseHasMoved(e);
}
}, interval);
</script>

我已经测试了这段代码工作文件:

var timeout = null;
var timee = '4000'; // default time for session time out.
$(document).bind('click keyup mousemove', function(event) {


if (timeout !== null) {
clearTimeout(timeout);
}
timeout = setTimeout(function() {
timeout = null;
console.log('Document Idle since '+timee+' ms');
alert("idle window");
}, timee);
});

你可以更优雅地使用Underscore.jsjQuery:

$('body').on("click mousemove keyup", _.debounce(function(){
// do preload here
}, 1200000)) // 20 minutes debounce

尝试freddoo的解决方案,但它没有工作1分钟超时,所以我已经稍微改变了它,以记录日期+时间,当用户最后点击页面和在我的timerIncrement函数,我计算当前时间和最后点击时间之间的差异,如果值恰好大于或等于超时值,然后我重定向:

var clickedDate = new Date();
var idleTime = 1; //


function timerIncrement() {


var nowDate = new Date();
var diffMs = (nowDate - clickedDate); //Milliseconds between now & the last time a user clicked somewhere on the page
var diffMins = Math.round(((diffMs % 86400000) % 3600000) / 60000); //Convert ms to minutes


if (diffMins >= idleTime) {
//Redirect user to home page etc...
}
}


$(document).ready(function () {


var idleInterval = setInterval(timerIncrement, 60000); // 1 minute


$(this).click(function (e) {
clickedDate = new Date();
});


});

改进Equiman(原始)的回答:

function idleLogout() {
var t;
window.onload = resetTimer;
window.onmousemove = resetTimer;
window.onmousedown = resetTimer;  // catches touchscreen presses as well
window.ontouchstart = resetTimer; // catches touchscreen swipes as well
window.ontouchmove = resetTimer;  // required by some devices
window.onclick = resetTimer;      // catches touchpad clicks as well
window.onkeydown = resetTimer;
window.addEventListener('scroll', resetTimer, true); // improved; see comments


function yourFunction() {
// your function for too long inactivity goes here
// e.g. window.location.href = 'logout.php';
}


function resetTimer() {
clearTimeout(t);
t = setTimeout(yourFunction, 10000);  // time is in milliseconds
}
}
idleLogout();

除了活动检测方面的改进,以及从documentwindow的更改之外,这个脚本实际上调用了函数,而不是让它闲置着。

它不会直接捕获零CPU使用情况,但这是不可能的,因为执行函数会导致CPU使用。用户不活动最终导致CPU使用率为零,因此它间接地捕捉到CPU使用率为零。

所有这些解决方案的问题,尽管是正确的,但在使用PHP、. net或在应用程序中考虑会话超时值集时,它们是不切实际的。ColdFusion开发人员的cfc文件。

上述解决方案设置的时间需要与服务器端会话超时同步。如果两者不同步,您可能会遇到一些问题,这些问题只会让用户感到沮丧和困惑。

例如,服务器端会话超时可能被设置为60分钟,但用户可能认为他/她是安全的,因为JavaScript空闲时间捕获增加了用户在单个页面上花费的总时间。用户可能花了很多时间填写一个很长的表单,然后去提交。会话超时可能在处理表单提交之前开始。

我倾向于只给用户180分钟,然后使用JavaScript自动注销用户。实际上,使用上面的一些代码来创建一个简单的计时器,但是没有捕获鼠标事件部分。

通过这种方式,我的客户端和服务器端时间完全同步。如果您在UI中向用户显示时间,就不会出现混乱。每次在CMS中访问一个新页面时,服务器端会话和JavaScript计时器都会被重置。简单而优雅。如果一个用户在一个页面上停留超过180分钟,首先我认为这个页面有问题。

以下是我找到的最佳解决方案:

当用户空闲时发生火灾事件

下面是JavaScript代码:

idleTimer = null;
idleState = false;
idleWait = 2000;


(function ($) {


$(document).ready(function () {


$('*').bind('mousemove keydown scroll', function () {


clearTimeout(idleTimer);


if (idleState == true) {


// Reactivated event
$("body").append("<p>Welcome Back.</p>");
}


idleState = false;


idleTimer = setTimeout(function () {


// Idle Event
$("body").append("<p>You've been idle for " + idleWait/1000 + " seconds.</p>");


idleState = true; }, idleWait);
});


$("body").trigger("mousemove");


});
}) (jQuery)
<script type="text/javascript">
var idleTime = 0;
$(document).ready(function () {
//Increment the idle time counter every minute.
idleInterval = setInterval(timerIncrement, 60000); // 1 minute


//Zero the idle timer on mouse movement.
$('body').mousemove(function (e) {
//alert("mouse moved" + idleTime);
idleTime = 0;
});


$('body').keypress(function (e) {
//alert("keypressed"  + idleTime);
idleTime = 0;
});


$('body').click(function() {
//alert("mouse moved" + idleTime);
idleTime = 0;
});
});


function timerIncrement() {
idleTime = idleTime + 1;
if (idleTime > 10) { // 10 minutes


window.location.assign("http://www.google.com");
}
}
</script>

我认为这个jQuery代码是一个完美的,虽然复制和修改从上面的答案!!

不要忘记在你的文件中包含jQuery库!

所有前面的答案都有一个始终活动的鼠标移动处理程序。如果处理程序是jQuery,那么jQuery执行的额外处理可以加起来。特别是当用户使用游戏鼠标时,每秒可能发生多达500个事件。

这个解决方案避免处理每一个鼠标移动事件。这导致一个小的时间误差,但你可以调整你的需要。

function setIdleTimeout(millis, onIdle, onUnidle) {
var timeout = 0;
startTimer();


function startTimer() {
timeout = setTimeout(onExpires, millis);
document.addEventListener("mousemove", onActivity);
document.addEventListener("keydown", onActivity);
document.addEventListener("touchstart", onActivity);
}
    

function onExpires() {
timeout = 0;
onIdle();
}


function onActivity() {
if (timeout) clearTimeout(timeout);
else onUnidle();
//since the mouse is moving, we turn off our event hooks for 1 second
document.removeEventListener("mousemove", onActivity);
document.removeEventListener("keydown", onActivity);
document.removeEventListener("touchstart", onActivity);
setTimeout(startTimer, 1000);
}
}

http://jsfiddle.net/9exz43v2/

我写了一个简单的jQuery插件,将做什么你正在寻找。

https://github.com/afklondon/jquery.inactivity

$(document).inactivity( {
interval: 1000, // the timeout until the inactivity event fire [default: 3000]
mouse: true, // listen for mouse inactivity [default: true]
keyboard: false, // listen for keyboard inactivity [default: true]
touch: false, // listen for touch inactivity [default: true]
customEvents: "customEventName", // listen for custom events [default: ""]
triggerAll: true, // if set to false only the first "activity" event will be fired [default: false]
});

脚本将监听鼠标,键盘,触摸和其他自定义事件不活动(空闲)并触发全局“活动”。和“;inactivity"事件。

试试这段代码。它工作得很完美。

var IDLE_TIMEOUT = 10; //seconds
var _idleSecondsCounter = 0;


document.onclick = function () {
_idleSecondsCounter = 0;
};


document.onmousemove = function () {
_idleSecondsCounter = 0;
};


document.onkeypress = function () {
_idleSecondsCounter = 0;
};


window.setInterval(CheckIdleTime, 1000);


function CheckIdleTime() {
_idleSecondsCounter++;
var oPanel = document.getElementById("SecondsUntilExpire");
if (oPanel)
oPanel.innerHTML = (IDLE_TIMEOUT - _idleSecondsCounter) + "";
if (_idleSecondsCounter >= IDLE_TIMEOUT) {
alert("Time expired!");
document.location.href = "SessionExpired.aspx";
}
}

如果你的目标是支持的浏览器(截至2018年12月的Chrome或Firefox),你可以尝试使用requestIdleCallback,并为不支持的浏览器添加requestIdleCallback垫片

下面是一个在Angular中完成的AngularJS服务。

/* Tracks now long a user has been idle.  secondsIdle can be polled
at any time to know how long user has been idle. */
fuelServices.factory('idleChecker',['$interval', function($interval){
var self = {
secondsIdle: 0,
init: function(){
$(document).mousemove(function (e) {
self.secondsIdle = 0;
});
$(document).keypress(function (e) {
self.secondsIdle = 0;
});
$interval(function(){
self.secondsIdle += 1;
}, 1000)
}
}
return self;
}]);

请记住,这个空闲检查器将为所有路由运行,因此在angular应用程序加载时应该在.run()中初始化它。然后你可以在每条路由中使用idleChecker.secondsIdle

myApp.run(['idleChecker',function(idleChecker){
idleChecker.init();
}]);

我的答案是受到维贾伊的回答的启发,但我认为这是一个更简短、更通用的解决方案,我想与任何人分享它可能有帮助。

(function () {
var minutes = true; // change to false if you'd rather use seconds
var interval = minutes ? 60000 : 1000;
var IDLE_TIMEOUT = 3; // 3 minutes in this example
var idleCounter = 0;


document.onmousemove = document.onkeypress = function () {
idleCounter = 0;
};


window.setInterval(function () {
if (++idleCounter >= IDLE_TIMEOUT) {
window.location.reload(); // or whatever you want to do
}
}, interval);
}());

按照目前的情况,这段代码将立即执行,并在3分钟内没有鼠标移动或按键后重新加载当前页面。

它利用普通JavaScript和立即调用的函数表达式以一种干净且自包含的方式处理空闲超时。

我也遇到过同样的问题,但我找到了一个很好的解决方案。

我使用jquery.idle,我只需要做:

$(document).idle({
onIdle: function(){
alert('You did nothing for 5 seconds');
},
idle: 5000
})

看到# EYZ0。

(仅供参考:查看后端事件跟踪Leads browserload)

您可以使用下面提到的解决方案

var idleTime;
$(document).ready(function () {
reloadPage();
$('html').bind('mousemove click mouseup mousedown keydown keypress keyup submit change mouseenter scroll resize dblclick', function () {
clearTimeout(idleTime);
reloadPage();
});
});
function reloadPage() {
clearTimeout(idleTime);
idleTime = setTimeout(function () {
location.reload();
}, 3000);
}

纯JavaScript,通过addEventListener正确设置重置时间和绑定:

(function() {


var t,
timeout = 5000;


function resetTimer() {
console.log("reset: " + new Date().toLocaleString());
if (t) {
window.clearTimeout(t);
}
t = window.setTimeout(logout, timeout);
}


function logout() {
console.log("done: " + new Date().toLocaleString());
}
resetTimer();


//And bind the events to call `resetTimer()`
["click", "mousemove", "keypress"].forEach(function(name) {
console.log(name);
document.addEventListener(name, resetTimer);
});


}());

我使用这种方法,因为您不需要在事件触发时不断重置时间。相反,我们只记录时间,这将生成空闲的起始点。

function idle(WAIT_FOR_MINS, cb_isIdle) {
var self = this,
idle,
ms = (WAIT_FOR_MINS || 1) * 60000,
lastDigest = new Date(),
watch;
//document.onmousemove = digest;
document.onkeypress = digest;
document.onclick = digest;


function digest() {
lastDigest = new Date();
}


// 1000 milisec = 1 sec
watch = setInterval(function() {
if (new Date() - lastDigest > ms && cb_isIdel) {
clearInterval(watch);
cb_isIdle();
}


}, 1000*60);
},

(部分灵感来自Equiman的答案的良好核心逻辑。)

sessionExpiration.js


sessionExpiration.js是轻量级的,但有效和可定制的。一旦实现,只在一行中使用:

sessionExpiration(idleMinutes, warningMinutes, logoutUrl);
  • 影响所有标签浏览器,而不是只有一个。
  • 纯JavaScript编写,没有依赖关系。完全客户端。
  • (如果需要的话。)警告标语倒计时时钟,由用户交互取消。
  • 简单地包括sessionExpiration.js,并调用该函数,参数为[1]空闲分钟数(在所有选项卡上),直到用户注销,[2]空闲分钟数,直到警告和倒计时显示,以及[3]注销url。
  • 将CSS放到样式表中。如果您愿意,可以自定义它。# EYZ0
  • 如果你想要警告横幅,那么你必须在你的页面(建议放在页脚)上放一个ID为sessExpirDiv的空div。
  • 现在,如果所有选项卡在给定的时间内都处于非活动状态,用户将自动注销。
  • 可选:您可以为该函数提供第四个参数(URL serverRefresh),这样当您与页面交互时,服务器端会话计时器也会刷新。

这是一个例子,如果你不改变CSS,它看起来是什么样子的。

demo_image

我编写了一个小的ES6类来检测活动,并在空闲超时时触发事件。它覆盖键盘,鼠标和触摸,可以是激活和去激活,并且有一个非常精简的API:

const timer = new IdleTimer(() => alert('idle for 1 minute'), 1000 * 60 * 1);
timer.activate();

确实依赖于jQuery,不过您可能需要通过Babel来运行它以支持旧的浏览器。

https://gist.github.com/4547ef5718fd2d31e5cdcafef0208096

Debounce其实是个好主意!下面是一个用于无jquery项目的版本:

const derivedLogout = createDerivedLogout(30);
derivedLogout(); // It could happen that the user is too idle)
window.addEventListener('click', derivedLogout, false);
window.addEventListener('mousemove', derivedLogout, false);
window.addEventListener('keyup', derivedLogout, false);


function createDerivedLogout (sessionTimeoutInMinutes) {
return _.debounce( () => {
window.location = this.logoutUrl;
}, sessionTimeoutInMinutes * 60 * 1000 )
}

我终于把这个工作在我的网站。我发现equiman的回答最有用。这个答案的问题是JavaScript中的alert()函数暂停了脚本执行。如果您像我一样希望发送一个警报,然后如果没有收到响应,站点将自动注销,那么暂停执行是一个问题。

解决方案是将alert()替换为自定义除法,即在这里

代码如下:(请注意:您需要更改第58行,以重定向到您的网站的适当URL)

var inactivityTracker = function () {


// Create an alert division
var alertDiv = document.createElement("div");
alertDiv.setAttribute("style","position: absolute;top: 30%;left: 42.5%;width: 200px;height: 37px;background-color: red;text-align: center; color:white");
alertDiv.innerHTML = "You will be logged out in 5 seconds!!";


// Initialise a variable to store an alert and logout timer
var alertTimer;
var logoutTimer;


// Set the timer thresholds in seconds
var alertThreshold = 3;
var logoutThreshold = 5;


// Start the timer
window.onload = resetAlertTimer;


// Ensure timer resets when activity logged
registerActivityLoggers(resetAlertTimer);


// ***** FUNCTIONS ***** //


// Function to register activities for alerts
function registerActivityLoggers(functionToCall) {
document.onmousemove = functionToCall;
document.onkeypress = functionToCall;
}


// Function to reset the alert timer
function resetAlertTimer() {
clearTimeout(alertTimer);
alertTimer = setTimeout(sendAlert, alertThreshold * 1000);
}


// Function to start logout timer
function startLogoutTimer() {
clearTimeout(logoutTimer);
logoutTimer = setTimeout(logout, logoutThreshold * 1000);
}


// Function to logout
function sendAlert() {


// Send a logout alert
document.body.appendChild(alertDiv);


// Start the logout timer
startLogoutTimer();


// Reset everything if an activity is logged
registerActivityLoggers(reset);
}


// Function to logout
function logout(){


//location.href = 'index.php';
}


// Function to remove alert and reset logout timer
function reset(){


// Remove alert division
alertDiv.parentNode.removeChild(alertDiv);


// Clear the logout timer
clearTimeout(logoutTimer);


// Restart the alert timer
document.onmousemove = resetAlertTimer;
document.onkeypress = resetAlertTimer;
}
};
<html>


<script type="text/javascript" src="js/inactivityAlert.js"></script>


<head>
<title>Testing an inactivity timer</title>
</head>
<body onload="inactivityTracker();" >
Testing an inactivity timer
</body>


</html>

我在这里提出的实现方法与其他答案在以下方面有所不同:

  • 空闲事件(默认命名为'idleTimeSeconds')每10秒触发一次,因此同一事件可以有多个订阅者
  • 每个文档实例只设置一个计时器
  • 定时器比idle事件触发更频繁(默认每1秒vs每10秒)-这将使默认的间隔精度
  • 记录空闲时间开始的时间戳,并用于计算总空闲时间;其他解决方案建议逐步向空闲时间计数器添加秒数,这是更少的代价,因为计时器的实际延迟可能比配置的要长,参见延迟的原因超过了指定的WindowOrWorkerGlobalScope.setTimeout()以获得示例。
  • 计时器永远不会取消/重置,正如其他一些解决方案所提出的;取消和重置计时器的成本更高

文件# EYZ0:

import $ from 'jquery';


export const IDLE_EVENT_NAME = 'idleTimeSeconds';


/**
* How often an 'idleTimeSeconds' event is fired on the document instance.
*
* @type {number}
*/
const IDLE_EVENT_RATE_SECONDS = 10;


/**
* How often the idle time is checked against the IDLE_EVENT_RATE_SECONDS.
*
* Should be much smaller than the value of IDLE_EVENT_RATE_SECONDS
* (the smaller the value is, the more precisely the event is fired) -
* because the actual delay may be longer, see "Reasons for delays
* longer than specified in WindowOrWorkerGlobalScope.setTimeout() for examples":
* https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Reasons_for_delays_longer_than_specified
*
* @type {number}
*/
const IDLE_TIMER_RATE_SECONDS = 1;


/**
* Because the actual timer delay may be longer, we track the timestamp
* when the idle time started, instead of incrementally adding to the total idle time.
* Having a starting point, we can always calculate the idle time precisely
* without accumulating delay errors.
*
* @type {number}
*/
let idleStartTimeMilliseconds;


/**
* Holds the interval reference.
*/
let idleInterval;


/**
* Holds the value of the latest idle time value
* for which the event was fired (integer value in seconds).
*
* The value is therefore factor of IDLE_EVENT_RATE_SECONDS.
*
* @type {number}
*/
let lastFiredSeconds;


const $document = $(document);


/**
* Resets the idle timer.
* Called on user interaction events, like keydown or touchstart.
*/
function resetIdleStartTime() {


// Reset the timestamp when the idle time started
idleStartTimeMilliseconds = (new Date).getTime();


// Reset the latest idle time value for which the even was fired
// (integer value in seconds).
lastFiredSeconds = 0;
}


/**
* Ticks every IDLE_TIMER_RATE_SECONDS, which is more often than the expected
* idle event firing rate.
*
* Fires the 'idleTimeSeconds' event on the document instance.
*/
function timerCallback() {


const nowMilliseconds = (new Date).getTime();
const idleTimeSeconds = Math.floor((nowMilliseconds - idleStartTimeMilliseconds) / 1000);


// When do we expect the idle event to be fired again?
// For example, if the event firing rate is 10 seconds,
// and last time it was fired at 40 seconds of idle time,
// the next one will be at 40 + 10 = 50 seconds.
const nextIdleSecondsToFire = lastFiredSeconds + IDLE_EVENT_RATE_SECONDS;


if (idleTimeSeconds >= nextIdleSecondsToFire) {


// Record last fired idle time that is factor of the rate,
// so that we keep firing the event as close to the desired rate as possible
lastFiredSeconds = nextIdleSecondsToFire;


$document.triggerHandler(IDLE_EVENT_NAME, [idleTimeSeconds]);
}
}


// Initialize the idle timer once only per the document instance
$(function() {


// Start the idle timer
idleInterval = setInterval(timerCallback, IDLE_TIMER_RATE_SECONDS * 1000);


// Reset the idle time start timestamp
$document.on('mousemove keydown mousedown touchstart', resetIdleStartTime);
});

示例用法(例如文件index.js):

import {IDLE_EVENT_NAME} from './Idle';
import $ from 'jquery';


$(function() {
$(document).on(IDLE_EVENT_NAME, function(e, idleSeconds) {
console.log('IDLE SECONDS:', idleSeconds);
});
});

示例输出(节选):

IDLE SECONDS: 580
IDLE SECONDS: 590
IDLE SECONDS: 600
IDLE SECONDS: 610
IDLE SECONDS: 620
IDLE SECONDS: 630
IDLE SECONDS: 640
IDLE SECONDS: 650
IDLE SECONDS: 660
IDLE SECONDS: 670
IDLE SECONDS: 680
IDLE SECONDS: 691
IDLE SECONDS: 700
IDLE SECONDS: 710
IDLE SECONDS: 720
IDLE SECONDS: 730
IDLE SECONDS: 740
IDLE SECONDS: 750
IDLE SECONDS: 761
IDLE SECONDS: 770
IDLE SECONDS: 780
IDLE SECONDS: 790
IDLE SECONDS: 800
IDLE SECONDS: 810
IDLE SECONDS: 820
IDLE SECONDS: 830
IDLE SECONDS: 840
IDLE SECONDS: 850
IDLE SECONDS: 860
IDLE SECONDS: 871
IDLE SECONDS: 880
IDLE SECONDS: 890
IDLE SECONDS: 900
IDLE SECONDS: 910
IDLE SECONDS: 921

上面的输出是当我切换到另一个选项卡并在那里做一些活动时产生的。可以看到,计时器有时会延迟(我想是因为在后台选项卡中,计时器以精确的速率被触发不是优先级)。但是空闲计时器仍然以正确的+/- 1秒的间隔触发。在本例中,1秒是空闲计时器的精度(通过Idle.js中的IDLE_TIMER_RATE_SECONDS常量配置)。

就像它可以得到的那样简单,检测鼠标移动的时间:

var idle = false;


document.querySelector('body').addEventListener('mousemove', function(e) {
if(idle!=false)
idle = false;
});


var idleI = setInterval(function()
{
if(idle == 'inactive')
{
return;
}


if(idle == true)
{
idleFunction();
idle = 'inactive';
return;
}


idle = true;
}, 30000); // half the expected time. Idle will trigger after 60 s in this case.


function idleFuntion()
{
console.log('user is idle');
}

您要求优雅,我创建了一个简单的类来支持惰性检查(具有空闲状态),除了命令方式(带有回调)之外。此外,这个类支持&;backtoactive&;当违反空闲时间时。

class Idle {
constructor(timeout = 10, idleCallback = null, backToActiveCallback = null, autoStart = true, backToActiveOnXHR = false) {
this.timeout = timeout
this.idleCallback = idleCallback
this.backToActiveCallback = backToActiveCallback
this.autoStart = autoStart // only F5
this.backToActiveOnXHR = backToActiveOnXHR
this.idle = false
this.timer = null
this.events = ['scroll', 'mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart']
this.init()
}


init() {
if(this.backToActiveOnXHR) {
this.events.push('load')
}
this.events.forEach(name => {
window.addEventListener(name, this.backToActive, true)
})
if(this.autoStart) {
this.backToActive()
}
}


goIdle = () => {
this.idle = true
if(!!this.idleCallback) {
this.idleCallback(this.timeout)
}
}


backToActive = () => {
if(this.idle) {
this.backToActiveCallback()
}
this.idle = false
clearTimeout(this.timer)
this.timer = setTimeout(this.goIdle, this.timeout * 1000)
}
}

用法:

let idleCallback = timeout => { console.log(`Went idle after ${timeout} seconds`) }
let backToActiveCallback = () => { console.log('Back to active') }
let idle = new Idle(30, idleCallback, backToActiveCallback)

devtools的结果:

// Went idle after 30 seconds <--- goes idle when no activity is detected
// Back to active <--- when the user is detected again

支持懒惰的好处:

setInterval(() => {
common.fetchApi('/api/v1/list', { status: idle.idle ? 'away' : 'online' }).then(/* show a list of elements */)
}, 1000 * 5)

你为什么要一张惰性支票?有时我们使用周期性的XHR(带setInterval),即当用户观看航班、乘车、电影、订单等列表时。对于每个XHR,我们可以添加关于他/她的活动状态(在线/离开)的信息,这样我们就可以了解系统中的活跃用户。

我的课程是基于Equiman的弗兰克Conijn的的答案。

您肯定想了解window.requestIdleCallback (),它在浏览器空闲期间对要调用的函数进行排队。

您可以在快速连接回购中看到这个API的优雅用法。

const requestIdleCallback = window.requestIdleCallback ||
function (cb) {
const start = Date.now();
return setTimeout(function () {
cb({
didTimeout: false,
timeRemaining: function () {
return Math.max(0, 50 - (Date.now() - start));
},
});
}, 1);
};

上面代码的意思是:如果浏览器支持requestIdleCallback(检查兼容性),就使用它。如果不支持,则使用setTimeout(()=> {}, 1)作为回退,它应该在事件循环结束时将要调用的函数排队。

然后你可以这样使用它:

requestIdleCallback(() => {...}, {
timeout: 2000
});

第二个参数是可选的,如果想确保函数被执行,可能需要设置timeout

基于equiman提供的输入:

class _Scheduler {
timeoutIDs;


constructor() {
this.timeoutIDs = new Map();
}


addCallback = (callback, timeLapseMS, autoRemove) => {
if (!this.timeoutIDs.has(timeLapseMS + callback)) {
let timeoutID = setTimeout(callback, timeLapseMS);
this.timeoutIDs.set(timeLapseMS + callback, timeoutID);
}


if (autoRemove !== false) {
setTimeout(
this.removeIdleTimeCallback, // Remove
10000 + timeLapseMS, // 10 secs after
callback, // the callback
timeLapseMS, // is invoked.
);
}
};


removeCallback = (callback, timeLapseMS) => {
let timeoutID = this.timeoutIDs.get(timeLapseMS + callback);
if (timeoutID) {
clearTimeout(timeoutID);
this.timeoutIDs.delete(timeLapseMS + callback);
}
};
}


class _IdleTimeScheduler extends _Scheduler {
events = [
'load',
'mousedown',
'mousemove',
'keydown',
'keyup',
'input',
'scroll',
'touchstart',
'touchend',
'touchcancel',
'touchmove',
];
callbacks;


constructor() {
super();
this.events.forEach(name => {
document.addEventListener(name, this.resetTimer, true);
});


this.callbacks = new Map();
}


addIdleTimeCallback = (callback, timeLapseMS) => {
this.addCallback(callback, timeLapseMS, false);


let callbacksArr = this.callbacks.get(timeLapseMS);
if (!callbacksArr) {
this.callbacks.set(timeLapseMS, [callback]);
} else {
if (!callbacksArr.includes(callback)) {
callbacksArr.push(callback);
}
}
};


removeIdleTimeCallback = (callback, timeLapseMS) => {
this.removeCallback(callback, timeLapseMS);


let callbacksArr = this.callbacks.get(timeLapseMS);
if (callbacksArr) {
let index = callbacksArr.indexOf(callback);
if (index !== -1) {
callbacksArr.splice(index, 1);
}
}
};


resetTimer = () => {
for (let [timeLapseMS, callbacksArr] of this.callbacks) {
callbacksArr.forEach(callback => {
// Clear the previous IDs
let timeoutID = this.timeoutIDs.get(timeLapseMS + callback);
clearTimeout(timeoutID);


// Create new timeout IDs.
timeoutID = setTimeout(callback, timeLapseMS);
this.timeoutIDs.set(timeLapseMS + callback, timeoutID);
});
}
};
}
export const Scheduler = new _Scheduler();
export const IdleTimeScheduler = new _IdleTimeScheduler();