Is it possible to detect "idle" time in JavaScript?
My primary use case probably would be to pre-fetch or preload content.
I define idle time as a period of user inactivity or without any CPU usage
Is it possible to detect "idle" time in JavaScript?
My primary use case probably would be to pre-fetch or preload content.
I define idle time as a period of user inactivity or without any CPU usage
Here is a simple script using jQuery that handles mousemove and keypress events. If the time expires, the page reloads.
<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
, which then gets evaluated as JavaScript. –
Hailstone keydown
event (because it will occur before keypress) + add click
event. And you can join events in one method: $(this).on('mousemove keydown click', function () { idleTime = 0; });
–
Tuna idleTime++;
instead of idleTime = idleTime + 1;
–
Barnet 0
to idleTime
. Hey, I even made jsperf test for you: jsperf.com/setting-values It's ~660 MOps/sec, do you really want to optimize it? Don't optimize prematurely, do it when you have a bottleneck. I can assure you, it will not be idleTime = 0
. –
Zootomy 'mousemove keydown click'
to use bit flags (Event.MOUSEMOVE | Event.KEYDOWN | Event.CLICK
), since they are waaaaay faster than string operations. But do you really want to do this? –
Zootomy With vanilla 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
}
};
And initialise the function where you need it (for example: onPageLoad).
window.onload = function() {
inactivityTime();
}
You can add more DOM events if you need to. Most used are:
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
Or register desired events using an array
window.addEventListener('load', resetTimer, true);
var events = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart'];
events.forEach(function(name) {
document.addEventListener(name, resetTimer, true);
});
DOM Events list: http://www.w3schools.com/jsref/dom_obj_event.asp
Remember to use window
, or document
according your needs. Here you can see the differences between them: What is the difference between window, screen, and document in JavaScript?
Code Updated with @frank-conijn and @daxchen improve: window.onscroll
will not fire if scrolling is inside a scrollable element, because scroll events don't bubble. In window.addEventListener('scroll', resetTimer, true)
, the third argument tells the listener to catch the event during the capture phase instead of the bubble phase.
onload
of body i hope. –
Molar document.onload = function () { inactivityTime(); }; document.onmousedown = function () { inactivityTime(); }; document.onkeypress = function () { inactivityTime(); }; document.ontouchstart = function () { inactivityTime(); };
–
Redingote var inactivityTime = function (timeout) {
so we have control of it form outside –
Forbidden postMessages
and sendMessage
. –
Redingote postMessage
and sendMessage
there is a lot of information available, but dealing with security on iframes is not a good idea, I'll recommend you use WebComponents instead of iframes. –
Redingote setTimeout()
from being negatively affected. https://mcmap.net/q/49287/-how-can-i-make-setinterval-also-work-when-a-tab-is-inactive-in-chrome –
Separatist resetTimer()
before returning from inactivityTime()
because document.onload
won't necessarily take care of that and maybe nobody ever touches the page. –
Rutherfurd Improving on Equiman's (original) answer:
function idleLogout() {
function yourFunction() {
// your function for too long inactivity goes here
// e.g. window.location.href = 'logout.php';
}
function resetTimer() {
var t = setTimeout(yourFunction, 600000); // time is in milliseconds (10 min)
clearTimeout(t); // is a global function, just as setTimeout
}
window.addEventListener('load', resetTimer, true);
window.addEventListener('mousemove', resetTimer, true);
window.addEventListener('mousedown', resetTimer, true);
window.addEventListener('touchstart', resetTimer, true);
window.addEventListener('touchmove', resetTimer, true);
window.addEventListener('click', resetTimer, true);
window.addEventListener('keydown', resetTimer, true);
window.addEventListener('scroll', resetTimer, true);
}
idleLogout();
Apart from the improvements regarding activity detection, and the change from document
to window
, this script actually calls the function, rather than letting it sit idle by.
It doesn't catch zero CPU usage directly, but that is impossible, because executing a function causes CPU usage. And user inactivity eventually leads to zero CPU usage, so indirectly it does catch zero CPU usage.
window.onscroll
will not fire if scrolling is inside a scrollable element, because scroll events don't bubble. Using window.addEventListener('scroll', resetTimer, true)
, the third argument tells the listener to catch event during capture
phase instead of bubble
phase (IE > 8), see this answer –
Lina document.onscroll
have the same problem, not firing if the scrolling is inside a scrollable child? –
Therapy addEventListener
instead of onscroll
. –
Lina window.addEventListener
for click,keypress, etc? I get why it is used for scroll here, but can it be used with others also? –
Spitzer addEventlistener
for all the other events as well. I guess the only downside is that the code gets a little more complicated. But I'm not sure, so you'll just have to try. Regarding onkeydown
: thanks for the tip. I edited my answer accordingly. –
Therapy window.ontouchmove
. I found this works well for touchscreen mobile devices –
Township window.ontouchstart
? –
Therapy window.ontouchstart
worked on my SurfacePro 7 (Chrome), but when i checked it on my Google Pixel 4 Mobile (Chrome Mobile) i had to include window.ontouchmove
for touch swipping to work. –
Township yourFunction
consists of window.location.href = 'logout.php'
, there are two options: 1. The user was done anyway and will close the tab or surf to another site. 2. The user was not done yet and will log in again, which will make the idleLogout
function to rerun in its entirety. If that's not what you mean, please rephrase the second problem that you see. Thanx. –
Therapy I have created a small library that does this:
https://github.com/shawnmclean/Idle.js
Description:
Tiny JavaScript library to report activity of user in the browser (away, idle, not looking at webpage, in a different tab, etc). that is independent of any other JavaScript libraries such as jQuery.
Visual Studio users can get it from NuGet by:
Install-Package Idle.js
Here is a rough jQuery implementation of tvanfosson's idea:
$(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 any action.
$(this).bind('mousemove keydown scroll click', function (e) {
idleTime = 0;
//Do something
});
function doPreload()
{
//Preload images, etc.
}
})
setInterval
a string! Just give a function as a variable! –
Saturday setInterval()
evaluates the expression in the global scope and thus it won't find the timerIncrement()
function that is inside the .ready handler function. This is yet another reason to NEVER pass strings to setInterval()
. Just pass an actual function reference and you won't have this problem because they are evaluated in the current scope. –
Carleencarlen Similar to Peter J's solution (with a jQuery custom event)...
// 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)))
You can do it more elegantly with Underscore.js and jQuery:
$('body').on("click mousemove keyup", _.debounce(function(){
// do preload here
}, 1200000)) // 20 minutes debounce
All the previous answers have an always-active mousemove handler. If the handler is jQuery, the additional processing jQuery performs can add up. Especially if the user is using a gaming mouse, as many as 500 events per second can occur.
This solution avoids handling every mousemove event. This result in a small timing error, but which you can adjust to your need.
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);
}
}
$(startTimer);
doing exactly? Does it do that startTimer()
should be doing? I didn't know that syntax in jQuery. AGAIN: Why not indicate clearly where the 'callback' should be passed? Oops!, I think those are the two parameters, "onIdle" and "onUnIdle". –
Region $(startTimer)
is equivalent to $(document).ready(startTimer)
, ensures that the DOM is ready before you hook the mousemove and keypress events. –
Mathison My answer was inspired by vijay's answer, but is a shorter, more general solution that I thought I'd share for anyone it might help.
(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);
}());
As it currently stands, this code will execute immediately and reload your current page after 3 minutes of no mouse movement or key presses.
This utilizes plain vanilla JavaScript and an immediately-invoked function expression to handle idle timeouts in a clean and self-contained manner.
I had the same issue and I found a quite good solution.
I used jquery.idle and I only needed to do:
$(document).idle({
onIdle: function(){
alert('You did nothing for 5 seconds');
},
idle: 5000
})
See JsFiddle demo.
(Just for information: see this for back-end event tracking Leads browserload)
keepTracking
option to false
. If your want to reset you could try to reinitialize is. Here is a modified example which would fire only once: jsfiddle.net/f238hchm/12 –
Inflation If you are targeting a supported browser (Chrome or Firefox as of December 2018) you can experiment with the requestIdleCallback and include the requestIdleCallback shim for unsupported browsers.
requestIdleCallback
(or requestAnimationFrame
) where it's supported. –
Binary You could probably hack something together by detecting mouse movement on the body of the form and updating a global variable with the last movement time. You'd then need to have an interval timer running that periodically checks the last movement time and does something if it has been sufficiently long since the last mouse movement was detected.
I wrote a small ES6 class to detect activity and otherwise fire events on idle timeout. It covers keyboard, mouse and touch, can be activated and deactivated and has a very lean API:
const timer = new IdleTimer(() => alert('idle for 1 minute'), 1000 * 60 * 1);
timer.activate();
It does not depend on jQuery, though you might need to run it through Babel to support older browsers.
(Partially inspired by the good core logic of Equiman's answer.)
sessionExpiration.js is lightweight yet effective and customizable. Once implemented, use in just one row:
sessionExpiration(idleMinutes, warningMinutes, logoutUrl);
This is an example of what it looks like in action, if you don't change the CSS.
Try this code. It works perfectly.
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";
}
}
<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>
I think this jQuery code is perfect one, though copied and modified from above answers!!
Do not forgot to include the jQuery library in your file!
Pure JavaScript with a properly set reset time and bindings via 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);
});
}());
The problem with all these solutions, although correct, is they are impractical, when taking into account the session timeout valuable set, using PHP, .NET or in the Application.cfc file for ColdFusion developers.
The time set by the above solution needs to sync with the server-side session timeout. If the two do not sync, you can run into problems that will just frustrate and confuse your users.
For example, the server side session timeout might be set to 60 minutes, but the user may believe that he/she is safe, because the JavaScript idle time capture has increased the total amount of time a user can spend on a single page. The user may have spent time filling in a long form, and then goes to submit it. The session timeout might kick in before the form submission is processed.
I tend to just give my users 180 minutes, and then use JavaScript to automatically log the user out. Essentially, using some of the code above, to create a simple timer, but without the capturing mouse event part.
In this way my client side and server-side time syncs perfectly. There is no confusion, if you show the time to the user in your UI, as it reduces. Each time a new page is accessed in the CMS, the server side session and JavaScript timer are reset. Simple and elegant. If a user stays on a single page for more than 180 minutes, I figure there is something wrong with the page, in the first place.
I wrote a simple jQuery plugin that will do what you are looking for.
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]
});
The script will listen for mouse, keyboard, touch and other custom events inactivity (idle) and fire global "activity" and "inactivity" events.
You can use the below mentioned solution
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);
}
Is it possible to have a function run every 10 seconds, and have that check a "counter" variable? If that's possible, you can have an on mouseover for the page, can you not?
If so, use the mouseover event to reset the "counter" variable. If your function is called, and the counter is above the range that you pre-determine, then do your action.
I have tested this code working file:
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);
});
Here is the best solution I have found:
Here is the 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)
I use this approach, since you don't need to constantly reset the time when an event fires. Instead, we just record the time, and this generates the idle start point.
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);
},
As simple as it can get, detect when the mouse moves only:
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');
}
Surely you want to know about window.requestIdleCallback(), which queues a function to be called during a browser's idle periods.
You can see an elegant usage of this API in the Quicklink repo.
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);
};
The meaning of the code above is: if the browser supports requestIdleCallback
(check the compatibility), uses it. If is not supported, uses a setTimeout(()=> {}, 1)
as fallback, which should queue the function to be called at the end of the event loop.
Then you can use it like this:
requestIdleCallback(() => {...}, {
timeout: 2000
});
The second parameter is optional, you might want to set a timeout
if you want to make sure the function is executed.
Based on the inputs provided by 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();
You could probably detect inactivity on your web page using the mousemove tricks listed, but that won't tell you that the user isn't on another page in another window or tab, or that the user is in Word or Photoshop, or WoW and just isn't looking at your page at this time.
Generally, I'd just do the prefetch and rely on the client's multi-tasking. If you really need this functionality, you do something with an ActiveX control in Windows, but it's ugly at best.
Here is an AngularJS service for accomplishing in 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;
}]);
Keep in mind this idle checker will run for all routes, so it should be initialized in .run()
on load of the angular app. Then you can use idleChecker.secondsIdle
inside each route.
myApp.run(['idleChecker',function(idleChecker){
idleChecker.init();
}]);
Debounce is actually a great idea! Here is a version for jQuery-free projects:
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 )
}
Well, you could attach a click or mousemove event to the document body that resets a timer.
Have a function that you call at timed intervals that checks if the timer is over a specified time (like 1000 milliseconds) and start your preloading.
JavaScript has no way of telling the CPU usage. This would break the sandbox JavaScript runs inside.
Other than that, hooking the page's onmouseover and onkeydown events would probably work.
You could also set use setTimeout in the onload event to schedule a function to be called after a delay.
// Call aFunction after 1 second
window.setTimeout(aFunction, 1000);
For other users with the same problem. Here is a function I just made up.
It does not run on every mouse movement the user makes, or clears a timer every time the mouse moves.
<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>
Tried freddoo's solution, but it didn't work for 1 minute timeouts, so I've changed it slightly to record the date+time when the user last clicked on the page and in my timerIncrement
function I calculate the difference between the current time and the last clicked time and if the value happens to be bigger or equal to the timeout value then I redirect:
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();
});
});
I finally got this working for my site. I found equiman's answer the most helpful. The problem with this answer is that the alert()
function in JavaScript pauses the script execution. Pausing execution is a problem if you want, as I did, an alert to be sent and then if no response received for the site to automatically logout.
The solution is to replace the alert()
with a custom division, described here.
Here's the code: (Note: you'll need to change line 58 to redirect to an appropriate URL for your site)
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>
The implementation I'm proposing here is different to the other answers in the following ways:
File Idle.js
:
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);
});
Example usage (e.g. file 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);
});
});
Example output (excerpt):
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
The output above is produced when I switch to another tab(s) and do some activities there for a while. As it can be seen, the timer is sometimes delayed (I suppose because it is not a priority for the timer to get fired with precise rate when in a background tab). But the idle timer still fires at the correct intervals +/- 1 second. In this case, 1 second is the precision of the idle timer (configured via the IDLE_TIMER_RATE_SECONDS
constant in Idle.js
).
You asked for elegancy, and I created a simple class to also support a lazy check (which has an idle state), aside to the imperative way (with callbacks). In addition, this class supports "backToActive" when the idle time is violated.
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)
}
}
Usage:
let idleCallback = timeout => { console.log(`Went idle after ${timeout} seconds`) }
let backToActiveCallback = () => { console.log('Back to active') }
let idle = new Idle(30, idleCallback, backToActiveCallback)
Result in devtools:
// Went idle after 30 seconds <--- goes idle when no activity is detected
// Back to active <--- when the user is detected again
The advantage of supporting laziness:
setInterval(() => {
common.fetchApi('/api/v1/list', { status: idle.idle ? 'away' : 'online' }).then(/* show a list of elements */)
}, 1000 * 5)
Why would you want a lazy check? Sometimes we use a periodic XHR (with setInterval), i.e., when a user watch a list of flights, rides, movies, orders, etc. With each XHR we then can add information about his/her activity status (online / away), so we have a sense of active users in our system.
My class is based on Equiman's and Frank Conijn's answers.
Here is my solution which also works for async f
To use: Idle(f, IdletimeoutInSec)
If f
returns false
the idle handler deregister and f
will no longer be called
function Idle(f, timeoutSec) {
const events = [ 'load', 'mousemove', 'mousedown', 'touchstart', 'touchmove', 'click', 'keydown', 'scroll']
let t, busy
function reset() {
if (busy) return
clearTimeout(t)
t = setTimeout(run, timeoutSec*1000)
}
async function run() {
if (busy) return
busy=true
if (await f() === false) {
events.forEach(s => window.removeEventListener(s, reset))
} else {
busy=false
reset()
}
}
events.forEach(s => window.addEventListener(s, reset, true))
}
let counter=0
function f() {
console.log('Idle')
if (++counter >3) return false
}
Idle(f, 2)
© 2022 - 2024 — McMap. All rights reserved.