How to detect idle time in JavaScript

Javascript

Javascript Problem Overview


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

Javascript Solutions


Solution 1 - Javascript

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>

Solution 2 - Javascript

Without using jQuery, only 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: https://stackoverflow.com/questions/9895202/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.

Solution 3 - Javascript

Improving on 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();

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.

Solution 4 - Javascript

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

Solution 5 - Javascript

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 mouse movement.
   $(this).mousemove(function(e){
      idleTime = 0;
   });
   
   function doPreload()
   {
     //Preload images, etc.
   }
   
})

Solution 6 - Javascript

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...
});

File jquery-idle-detect.js
(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)))

Solution 7 - Javascript

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

Solution 8 - Javascript

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);
    }
    
    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);
    }
}

http://jsfiddle.net/jndxq51o/

Solution 9 - Javascript

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.

Solution 10 - Javascript

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)

Solution 11 - Javascript

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.

Solution 12 - Javascript

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.

Solution 13 - Javascript

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.

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

Solution 14 - Javascript

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";
    }
}

Solution 15 - Javascript

(Partially inspired by the good core logic of Equiman's answer.)

sessionExpiration.js


sessionExpiration.js is lightweight yet effective and customizable. Once implemented, use in just one row:

sessionExpiration(idleMinutes, warningMinutes, logoutUrl);
  • Affects all tabs of the browser, not just one.
  • Written in pure JavaScript, with no dependencies. Fully client side.
  • (If so wanted.) Has warning banner and countdown clock, that is cancelled by user interaction.
  • Simply include the sessionExpiration.js, and call the function, with arguments [1] number of idle minutes (across all tabs) until user is logged out, [2] number of idle minutes until warning and countdown is displayed, and [3] logout url.
  • Put the CSS in your stylesheet. Customize it if you like. (Or skip and delete banner if you don't want it.)
  • If you do want the warning banner however, then you must put an empty div with ID sessExpirDiv on your page (a suggestion is putting it in the footer).
  • Now the user will be logged out automatically if all tabs have been inactive for the given duration.
  • Optional: You may provide a fourth argument (URL serverRefresh) to the function, so that a server side session timer is also refreshed when you interact with the page.

This is an example of what it looks like in action, if you don't change the CSS.

demo_image

Solution 16 - Javascript

<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!

Solution 17 - Javascript

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);
  });

}());

Solution 18 - Javascript

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.

Solution 19 - Javascript

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.

Solution 20 - Javascript

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);
}

Solution 21 - Javascript

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.

Solution 22 - Javascript

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);
	});

Solution 23 - Javascript

Here is the best solution I have found:

Fire Event When User is Idle

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)

Solution 24 - Javascript

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);
},

Solution 25 - Javascript

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');
}

Solution 26 - Javascript

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();

Solution 27 - Javascript

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.

Solution 28 - Javascript

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();
}]);

Solution 29 - Javascript

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 )
}

Solution 30 - Javascript

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.

Solution 31 - Javascript

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.

Solution 32 - Javascript

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);

Solution 33 - Javascript

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>

Solution 34 - Javascript

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();
    });

});

Solution 35 - Javascript

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>

Solution 36 - Javascript

The implementation I'm proposing here is different to the other answers in the following ways:

  • the idle event (by default named 'idleTimeSeconds') is fired every 10 seconds, so you can have multiple subscribers to the same event
  • there is only one timer set per the document instance
  • the timer is fired more often then the idle event (by default every 1 second vs every 10 seconds) - this will make for the default interval precision
  • the timestamp of when the idle time started is recorded and is used to calculate the total idle time; other solutions propose to incrementally add seconds to the idle time counter, which is less prices because the actual delay of a timer may be longer than configured, see "Reasons for delays longer than specified in WindowOrWorkerGlobalScope.setTimeout()" for examples.
  • the timer is never cancelled / reset, as proposed by some other solutions; cancelling and resetting timers is more expensive

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).

Solution 37 - Javascript

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCherianView Question on Stackoverflow
Solution 1 - JavascriptfreddooView Answer on Stackoverflow
Solution 2 - JavascriptequimanView Answer on Stackoverflow
Solution 3 - JavascriptFrank Conijn - Support UkraineView Answer on Stackoverflow
Solution 4 - JavascriptShawn McleanView Answer on Stackoverflow
Solution 5 - JavascriptPeter JView Answer on Stackoverflow
Solution 6 - JavascriptTracker1View Answer on Stackoverflow
Solution 7 - JavascriptKruttikView Answer on Stackoverflow
Solution 8 - JavascriptSarsaparillaView Answer on Stackoverflow
Solution 9 - JavascriptjohnnyRoseView Answer on Stackoverflow
Solution 10 - JavascriptDDanView Answer on Stackoverflow
Solution 11 - JavascripttvanfossonView Answer on Stackoverflow
Solution 12 - JavascriptrajsiteView Answer on Stackoverflow
Solution 13 - JavascriptCapi EtherielView Answer on Stackoverflow
Solution 14 - JavascriptvijayView Answer on Stackoverflow
Solution 15 - JavascriptFomView Answer on Stackoverflow
Solution 16 - JavascriptSunil GargView Answer on Stackoverflow
Solution 17 - JavascriptJackTheKnifeView Answer on Stackoverflow
Solution 18 - JavascriptCharles RobertsonView Answer on Stackoverflow
Solution 19 - JavascriptAlexandros Filos KaparelosView Answer on Stackoverflow
Solution 20 - JavascriptAbhishekView Answer on Stackoverflow
Solution 21 - JavascriptMark RulloView Answer on Stackoverflow
Solution 22 - JavascriptVishal JauraView Answer on Stackoverflow
Solution 23 - JavascriptCaseyView Answer on Stackoverflow
Solution 24 - JavascriptMartinWebbView Answer on Stackoverflow
Solution 25 - JavascriptlucssView Answer on Stackoverflow
Solution 26 - JavascriptMurali MohanView Answer on Stackoverflow
Solution 27 - JavascriptWalden LeverichView Answer on Stackoverflow
Solution 28 - JavascriptsteampoweredView Answer on Stackoverflow
Solution 29 - JavascriptGleb DolzikovView Answer on Stackoverflow
Solution 30 - JavascriptCajotaferView Answer on Stackoverflow
Solution 31 - JavascriptEric WendelinView Answer on Stackoverflow
Solution 32 - JavascriptPowerlordView Answer on Stackoverflow
Solution 33 - JavascriptChristoffer TerpView Answer on Stackoverflow
Solution 34 - JavascriptDenys WesselsView Answer on Stackoverflow
Solution 35 - JavascriptJoseph CrispellView Answer on Stackoverflow
Solution 36 - JavascriptMeglioView Answer on Stackoverflow
Solution 37 - JavascriptTechWisdomView Answer on Stackoverflow