【发布时间】:2021-08-23 09:32:23
【问题描述】:
是否可以在 JavaScript 中检测“idle”时间?
我的主要用例可能是预取或预加载内容。
我将空闲时间定义为用户不活动或没有任何 CPU 使用的一段时间
【问题讨论】:
-
如果有人有关于 CPU 空闲的答案,我会非常感兴趣。在大多数情况下,用户交互不会非常空闲。
标签: javascript
是否可以在 JavaScript 中检测“idle”时间?
我的主要用例可能是预取或预加载内容。
我将空闲时间定义为用户不活动或没有任何 CPU 使用的一段时间
【问题讨论】:
标签: javascript
这是一个使用 jQuery 处理 mousemove 和 keypress 事件的简单脚本。 如果时间到期,页面会重新加载。
<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>
【讨论】:
setInterval,然后将其计算为 JavaScript。
idleTime++; 而不是idleTime = idleTime + 1;
'mousemove keydown click' 这样的字符串的函数来使用位标志(Event.MOUSEMOVE | Event.KEYDOWN | Event.CLICK),因为它们比字符串操作。但是你真的想要这样做吗?
不使用 jQuery,只使用原生 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
记得根据需要使用window,或document。在这里你可以看到它们之间的区别:What is the difference between window, screen, and document in JavaScript?
代码更新:如果滚动在可滚动元素内,window.onscroll 将不会触发,因为滚动事件不会冒泡。在window.addEventListener('scroll', resetTimer, true) 中,第三个参数告诉监听器在捕获阶段而不是冒泡阶段捕获事件。
【讨论】:
document.onload = function () { inactivityTime(); }; document.onmousedown = function () { inactivityTime(); }; document.onkeypress = function () { inactivityTime(); }; document.ontouchstart = function () { inactivityTime(); };
var inactivityTime = function (timeout) { 所以我们可以在外部控制它
改进Equiman's (original) answer:
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();
除了关于活动检测的改进以及从document 到window 的更改之外,此脚本实际上调用了该函数,而不是让它闲置。
它不会直接捕获零 CPU 使用率,但这是不可能的,因为执行函数会导致 CPU 使用率。并且用户不活动最终导致 CPU 使用率为零,因此它间接地捕获了 CPU 使用率为零。
【讨论】:
window.onscroll 在可滚动元素内滚动时不会触发,因为滚动事件不会冒泡。使用window.addEventListener('scroll', resetTimer, true),第三个参数告诉侦听器在capture 阶段而不是bubble 阶段(IE > 8)捕获事件,see this answer
document.onscroll 不是有同样的问题,如果滚动是在可滚动的子对象内部,则不会触发?
addEventListener 而不是onscroll。
我创建了一个小型库来执行此操作:
https://github.com/shawnmclean/Idle.js
说明:
用于报告用户在浏览器中的活动的小型 JavaScript 库 (离开,闲置,不看网页,在不同的标签等)。独立于任何 其他 JavaScript 库,例如 jQuery。
Visual Studio 用户可以通过以下方式从 NuGet 获取它:
Install-Package Idle.js
【讨论】:
这是tvanfosson's idea的粗略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.
}
})
【讨论】:
setInterval 一个字符串!只要给一个函数作为变量!
setInterval() 会在全局范围内评估表达式,因此它不会找到 .ready 处理函数内的 timerIncrement() 函数。这是永远不要将字符串传递给setInterval() 的另一个原因。只需传递一个实际的函数引用,您就不会遇到这个问题,因为它们是在当前范围内评估的。
类似于Peter J's solution(带有 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...
});
(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)))
【讨论】:
您可以使用Underscore.js 和jQuery 更优雅地做到这一点:
$('body').on("click mousemove keyup", _.debounce(function(){
// do preload here
}, 1200000)) // 20 minutes debounce
【讨论】:
我的回答受到vijay's answer 的启发,但它是一个更简短、更通用的解决方案,我认为我会分享给任何可能有帮助的人。
(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 和 immediately-invoked function expression 以干净且独立的方式处理空闲超时。
【讨论】:
onclick 分配,因为除了onmousemove 之外它可能没有必要,但显然这些以编程方式触发的任何事件都将继续重置idleCounter。我不确定你为什么要模拟用户交互而不是仅仅调用一个函数,但如果这是你出于某种原因需要做的事情,这个答案显然对你不起作用,我的大多数其他答案也不会我看过这个问题。
所有以前的答案都有一个始终处于活动状态的 mousemove 处理程序。如果处理程序是 jQuery,则 jQuery 执行的附加处理可以累加。尤其是当用户使用游戏鼠标时,每秒可能发生多达 500 个事件。
此解决方案避免处理每个 mousemove 事件。这会导致一个小的计时错误,但您可以根据需要进行调整。
function setIdleTimeout(millis, onIdle, onUnidle) {
var timeout = 0;
startTimer();
function startTimer() {
timeout = setTimeout(onExpires, millis);
document.addEventListener("mousemove", onActivity);
document.addEventListener("keydown", 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);
setTimeout(startTimer, 1000);
}
}
【讨论】:
$(startTimer) 等价于$(document).ready(startTimer),确保 DOM 在您挂钩 mousemove 和 keypress 事件之前准备就绪。
我遇到了同样的问题,我找到了一个很好的解决方案。
我使用了jquery.idle,我只需要这样做:
$(document).idle({
onIdle: function(){
alert('You did nothing for 5 seconds');
},
idle: 5000
})
(仅供参考:see this for back-end event tracking Leads browserload)
【讨论】:
keepTracking 选项设置为false。如果您想重置,您可以尝试重新初始化。这是一个只触发一次的修改示例:jsfiddle.net/f238hchm/12
您可以通过检测表单主体上的鼠标移动并使用上次移动时间更新全局变量来组合一些东西。然后,您需要运行一个间隔计时器,它会定期检查最后一次移动时间,并在检测到最后一次鼠标移动后的时间足够长时执行某些操作。
【讨论】:
如果您的目标是 supported browser(Chrome 或 Firefox,截至 2018 年 12 月),您可以尝试使用 requestIdleCallback,并为不受支持的浏览器添加 requestIdleCallback shim。
【讨论】:
requestIdleCallback(或requestAnimationFrame)。
我编写了一个小的ES6 类来检测活动,并在空闲超时时触发事件。它涵盖了键盘、鼠标和触摸,可以激活和停用,并且具有非常精简的 API:
const timer = new IdleTimer(() => alert('idle for 1 minute'), 1000 * 60 * 1);
timer.activate();
它确实不依赖于 jQuery,但您可能需要通过 Babel 运行它以支持旧版浏览器。
【讨论】:
试试这个代码。效果很好。
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";
}
}
【讨论】:
(部分灵感来自Equiman's answer 的良好核心逻辑。)
sessionExpiration.js 是轻量级但有效且可定制的。实施后,仅在一行中使用:
sessionExpiration(idleMinutes, warningMinutes, logoutUrl);
如果您不更改 CSS,这是一个实际效果示例。
【讨论】:
<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 库!
【讨论】:
通过addEventListener 正确设置重置时间和绑定的纯 JavaScript:
(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);
});
}());
【讨论】:
所有这些解决方案的问题,虽然是正确的,但它们是不切实际的,当考虑到会话超时有价值的设置时,使用 PHP、.NET 或在 ColdFusion 开发人员的 Application.cfc 文件中。
上述方案设置的时间需要与服务器端会话超时时间同步。如果两者不同步,您可能会遇到只会让您的用户感到沮丧和困惑的问题。
例如,服务器端会话超时可能设置为 60 分钟,但用户可能认为他/她是安全的,因为 JavaScript 空闲时间捕获增加了用户可以在单次上花费的总时间页。用户可能已经花时间填写了一个长表单,然后去提交它。会话超时可能会在处理表单提交之前启动。
我倾向于只给我的用户 180 分钟,然后使用 JavaScript 自动将用户注销。本质上,使用上面的一些代码来创建一个简单的计时器,但没有捕获鼠标事件部分。
通过这种方式,我的客户端和服务器端时间完美同步。如果您在 UI 中向用户显示时间,则不会造成混淆,因为它会减少。每次在 CMS 中访问新页面时,都会重置服务器端会话和 JavaScript 计时器。简单而优雅。如果用户在一个页面上停留超过 180 分钟,我首先认为该页面有问题。
【讨论】:
您可以使用下面提到的解决方案
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);
}
【讨论】:
我编写了一个简单的 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]
});
该脚本将侦听鼠标、键盘、触摸和其他自定义事件不活动(空闲)并触发全局“活动”和“不活动”事件。
【讨论】:
我已经测试过这个代码工作文件:
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);
});
【讨论】:
是否可以让函数每 10 秒运行一次,并检查“计数器”变量?如果可能的话,您可以在页面上设置鼠标悬停,不是吗?
如果是这样,请使用鼠标悬停事件来重置“计数器”变量。如果您的函数被调用,并且计数器高于您预先确定的范围,则执行您的操作。
【讨论】:
这是我找到的最佳解决方案:
这里是 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)
【讨论】:
我使用这种方法,因为您不需要不断地重置事件触发的时间。相反,我们只记录时间,这会生成空闲起点。
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);
},
【讨论】:
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();
【讨论】:
尽可能简单,只检测鼠标何时移动:
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');
}
【讨论】:
这是一个 AngularJS 服务,用于在 Angular 中完成。
/* 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();
}]);
【讨论】:
您肯定想知道window.requestIdleCallback(),它在浏览器空闲期间将要调用的函数排队。
您可以在Quicklink repo 中看到此 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。
【讨论】:
去抖动实际上是个好主意!这是一个无 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 )
}
【讨论】: