How to detect idle time in JavaScript
Asked Answered
R

38

597

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

Rondi answered 20/3, 2009 at 19:15 Comment(2)
check github.com/kidh0/jquery.idleYoong
I'd be very interested if someone had an answer regarding CPU idle. User interaction isn't going to be significantly idle in majority of cases.Mar
M
499

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>
Musky answered 27/10, 2010 at 2:17 Comment(16)
You're missing a semicolon after the $(document).ready(function() body. Also, in the call to setInterval, it won't work with quotes around the function name and you don't need the parentheses after it. Just: setInterval(timerIncrement, 60000)Acanthocephalan
Agreed. Very simple and effective implementation. Also allows for quick addition of extra interval based function calls. Perfect for an auto logout script with notificationMummer
@Jesse: Your suggestions are all good, this is how the code should be. But I just wanted to point out that even without these changes, the code is fully functional. Semicolons at the end of an expression statement are optional and you can in fact pass a string to setInterval, which then gets evaluated as JavaScript.Hailstone
I think that it will be better to use 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
You could simply use idleTime++; instead of idleTime = idleTime + 1;Barnet
Is this not heavy on a user's system? Let's say, a user with a fairly old browser on a not that heavy pc, is working in a javascript application for half a day, and it is processing these functions every time the user moves his mouse... I wonder if this won't affect the user's experience...Mojica
Same question here: why fire a handler for every mousemove / mouse click? Won't that affect perfomance? Perhaps it's better to reset the idle timer when an actual function of the page is triggered? (clicking a button, scrolling a text?)Bitters
@Mojica About the performance - it costs little to none to assign 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
@PietBinnenbocht Also, if you start optimizing things like this, you may as well change every function that takes strings like '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
Will this interfere with existing mousemove event handlers?Joanne
@quillbreaker: No, this will attach a function to the mousemove-event.Saury
What happens when multiple tabs are open and user forgot to interact with one of them and lies in background?Uninhibited
All of these great contributions and not one answer accepted? Shame.Centesimal
still not sure how this helps with cpu usage. Please adviseOptimize
On the home screen the code works perfectly, but when I open a modal screen with iframe, the code doesn't work. How to make it work on the entire screen, including those that open via modal with iframe?Linguini
Working fine. Good example. I have tried this in my angular application without using libraries. Thanks 🙏👍Apron
R
499

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.

Redingote answered 12/4, 2012 at 14:47 Comment(19)
definitely resetting a timer is a more direct/intuitive and accurate approach than having a timeout do its thing, only to keep an integer count of another thing, when the timer itself can BE the (high-precision) counter.Mahmoud
When it is called in front end?Molar
@Molar all this is the function... you define when call it using "onload", an "anonymous function" or with an "event".Redingote
Like this jsfiddle.net/mpsbhat/6b6mja5t ?? In the fiddle, it will call onload of body i hope.Molar
@Molar just add an console.log or an alert an see if works. Or register this events: document.onload = function () { inactivityTime(); }; document.onmousedown = function () { inactivityTime(); }; document.onkeypress = function () { inactivityTime(); }; document.ontouchstart = function () { inactivityTime(); };Redingote
Yeah... Working. jsfiddle.net/mpsbhat/6b6mja5t/1. Thanks @RedingoteMolar
Would be much better to have a flag var notidle; set that flag = true only on the events. Then in the resetTimer function test if the notidle flag is true, if it is reset the timer their, or call logout. This will remove the complexity overhead of constantly resetting the timer.Incapacitate
@Incapacitate maybe, but i think is more complex using a global variable than calling the function in any "document" or "window".Redingote
Simple / elegant solution I just updated and added the timeout as argument like: var inactivityTime = function (timeout) { so we have control of it form outsideForbidden
@CharlesL. just curious why you don't listen for window.onclick? Is it because this would be redundant with mousedown?Postulate
@Postulate It's been too long for me to remember for sure, but my guess would be the same as yours, it seems redundant.Ucayali
On the home screen the code works perfectly, but when I open a modal screen with iframe, the code doesn't work. How to make it work on the entire screen, including those that open via modal with iframe?Linguini
@tiago thas because each embedded browsing context has its own session history and document. You can "talk" between embed and parent browsing context across postMessages and sendMessage.Redingote
@Linguini I've implemented yet on a project, but it's proprietary code then I can't share it. You can google for: 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
When I close the flap of my machine at night and open it in the morning, it seems to me that the timer just resumes and never logs me out. What can be done in such a case?Ankylose
@Ankylose you need better control sessions in the backend. That kind of control doesn't be recommended done in the front end. Send a keepalive request with this amount of time should respond to as a failure.Redingote
@Redingote This solution won't work properly when the tab is inactive, correct? It's my understanding that you'd need to use web workers to avoid setTimeout() from being negatively affected. https://mcmap.net/q/49287/-how-can-i-make-setinterval-also-work-when-a-tab-is-inactive-in-chromeSeparatist
@im1dermike yes, because it's a client-side solution. Actually (9 years later) I think there is a better approach using JWT than sessions to handle timeouts.Redingote
Call resetTimer() before returning from inactivityTime() because document.onload won't necessarily take care of that and maybe nobody ever touches the page.Rutherfurd
T
126

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.

Therapy answered 28/7, 2014 at 6:59 Comment(22)
Just wanted to point out that 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 answerLina
@Lina -- Doesn't document.onscroll have the same problem, not firing if the scrolling is inside a scrollable child?Therapy
Yes, the point I was saying is to useaddEventListener instead of onscroll.Lina
@Lina -- OK, I was wondering whether you meant that, to work around it, but it's clear now. I edited the answer accordingly. Thanks for the comment.Therapy
I'll update my answer with this important information, to avid other copy and paste my mistake. Thanks @Lina and FrankRedingote
is there a downside to using window.addEventListener for click,keypress, etc? I get why it is used for scroll here, but can it be used with others also?Spitzer
btw onkeypress is depricated use onkeydownSpitzer
@Spitzer - I'd say you could use 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
On the home screen the code works perfectly, but when I open a modal screen with iframe, the code doesn't work. How to make it work on the entire screen, including those that open via modal with iframe?Linguini
@Linguini — Sorry the for late reply, I haven't been on SO for a while. I'm afraid it can only be made to work on an iframe if you can include the script in the source code of the iframe page. An iframe is a child page in a mother page, and need their own scripts.Therapy
There isn't a user by the name "Equiman" here. What answer does it refer to?Gombosi
@PeterMortensen — There isn't a user by the name 'Equiman'? While you edited my post so that it links the post of ..... Equiman?Therapy
Thanks for the post im using this myself. Why have you not included window.ontouchmove . I found this works well for touchscreen mobile devicesTownship
@Township — I'm too busy to test it right now, but isn't that covered by window.ontouchstart?Therapy
@FrankConijn - I found that 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
@Township — OK, thanks for the tip. I added it.Therapy
There are multiple problems here - you are removing any other handlers that may be attached to ie window.onload - you need to use AddEventListener everywhere, because of that you can't call your function multiple times. Also note how after 'yourfunction` runs the idle timeout is disabled until a mouse gestureDownturn
@Downturn -- You were right. I updated the answer regarding the addEventListener. But how would you solve the second problem? I'm not sure how to do that; feel free to edit it.Therapy
@FrankConijn-SupportUkraine see my answer below https://mcmap.net/q/64270/-how-to-detect-idle-time-in-javascriptDownturn
@Downturn -- I'm afraid I find your syntax much more difficult than mine. Also, I actually wonder what the second problem that you described really is. If my 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
that's a very particular case where you just close the page .. think if yourFunction ie shows an alert. You idel, the alert is shown, you press OK. If you then don't do anything you will not get another idle but if you move a bit then stop you will get itDownturn
@Downturn -- That would be ideal, but not necessary. If a user doesn't scroll for 10 minutes, they're probably looking at another tab or even another program.Therapy
F
34

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
Freewheeling answered 25/4, 2013 at 19:5 Comment(0)
C
33

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.
   }
   
})
Chessy answered 20/3, 2009 at 19:46 Comment(4)
This solution doesn't consider keyboard events.Serenaserenade
Never pass setInterval a string! Just give a function as a variable!Saturday
This won't actually work because passing a string to 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
Thanks, I was not aware of the need to NEVER pass strings to setInterval. Updated my answer.Chessy
S
25

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 answered 20/3, 2009 at 21:10 Comment(0)
S
23

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
Schnauzer answered 25/6, 2014 at 22:7 Comment(0)
M
21

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

http://jsfiddle.net/9exz43v2/

Mathison answered 31/7, 2015 at 17:24 Comment(6)
Will this work automatically if placed on a page, or does it need to be inside a $(document).ready() wrapper? Thank you! Also, where is the portion which performs an action when the timer expires?Exfoliate
You can call this any time, even before document is ready. You pass a function 'callback' that will be called when the timer expires.Mathison
What's this $(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
The $(startTimer) is equivalent to $(document).ready(startTimer), ensures that the DOM is ready before you hook the mousemove and keypress events.Mathison
+1 This is what I do - mousemove handlers contribute to sluggishness and reduced battery life, so only periodically turning it on is a great idea if you can afford the minor timing error. I usually use idle time detection for automatic session expiration warnings (eg "Are you still there?"), so I tend to have many minutes before a user goes "idle", in which case a small timing error is totally irrelevant.Geotectonic
It is better to use "keydown" than "keypress" because arrow keys are not detected by the "keypress" event. So, if the user is navigating the page using the arrow keys, it will become Idle anyways.Denys
F
20

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.

Flatfish answered 29/12, 2015 at 18:21 Comment(1)
document.onclick considers javascript functions using .trigger('click') which I wrote as automated. So it's not really a user interaction but it will reset the idleCounter in this casePilocarpine
I
19

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)

Inflation answered 2/3, 2016 at 5:28 Comment(10)
How can i stop this function, They stated an event idle:stop but I honestly dont know how to use this. I want that if i moved to next page (ajax based so only fragment of HTML page updated) then idle function stops. Did you know how to achieve this?Farrar
Here it says: "idle:stop": will stop and remove user trackingInflation
I already have read but could not figure out how to use this, Could you help me ?Farrar
If you want it to fire only once, you can set the 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/12Inflation
No I do not fire once, keepTracking should be true, but on navigation to other page I want to stop thisFarrar
What to do If I want same functionality for 20 minutes?Dor
Since I am passing the time in idle like this ... idle: 20*60*1000 ..which is not working.Dor
@ShreyaRawal try like this: idle: (20*60*1000). Use parentheses. The expression yields to int and it will work. See example: jsfiddle.net/ddan/f238hchm/24 (at the end of the JS snippet)Inflation
It is not working for values more than 5 minutes @InflationDor
I just tried your link jsfiddle.net/f238hchm/25 (on Chrome) and it worked fine. I also have pages executing JS code after 30 mins of inactivity. It should work fine to numbers higher than 5 min. What is the browser you are testing with?Inflation
S
17

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.

Samirasamisen answered 20/10, 2015 at 23:13 Comment(4)
And the leave the other browsers alone?Therapy
Shim cover all browsers. And this was the only answer! Other slow down answers is about mouse. The interactivity or CPU was only said as an example. How to detect CPU idle with a mouse solution?Outstanding
This should be the only accepted answer. If the work you're doing could delay the main thread, it can cause hitches in animations, etc, so you should use requestIdleCallback (or requestAnimationFrame) where it's supported.Binary
this should be the accepted answer in 2023Slapup
O
14

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.

Okeechobee answered 20/3, 2009 at 19:20 Comment(2)
Important to note that the script will only be able to detect motion on the body of the page, not all user input. I don't think there's a way to get CPU or process info from javascript.Marissamarist
I took the liberty of implementing your idea in jQuery.Chessy
P
14

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

Puzzlement answered 9/7, 2018 at 22:7 Comment(0)
A
8

(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

Agentive answered 2/7, 2018 at 22:21 Comment(0)
U
7

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";
    }
}
Upwards answered 21/8, 2015 at 11:12 Comment(1)
An explanation would be in order. E.g., why does it work perfectly? What is the idea/gist? How is it different from previous answers?Gombosi
C
6
<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!

Chiles answered 21/4, 2015 at 9:16 Comment(1)
A version of this is what I ended up using. I just pointed my window.location to a route that would unset my session variables and display a logout message.Fabiano
D
5

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

}());
Dermot answered 30/9, 2016 at 17:41 Comment(1)
Could use a scroll listener and debouncing as wellCalandra
D
4

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.

Dieppe answered 4/10, 2014 at 15:13 Comment(1)
Yep, that's why I'm only doing this after getting rid of server side sessions and loading everything from html files.Quarterhour
F
3

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.

Flossie answered 3/8, 2015 at 9:37 Comment(1)
Is there really a need for a delay, isn’t triggering a custom event from a custom event handler enough?Lallygag
W
3

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);
}
Windstorm answered 27/9, 2016 at 13:10 Comment(0)
T
2

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.

Taw answered 20/3, 2009 at 19:20 Comment(0)
A
2

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);
    });
Alcestis answered 3/1, 2014 at 11:38 Comment(0)
R
2

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)
Reich answered 16/2, 2015 at 21:54 Comment(0)
I
2

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);
},
Incapacitate answered 2/12, 2016 at 16:8 Comment(0)
F
2

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');
}
Firkin answered 7/1, 2019 at 22:59 Comment(1)
What do you mean by "moves only"? In contrast to clicking? Or something else?Gombosi
O
2

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.

Outplay answered 16/6, 2020 at 8:10 Comment(1)
I dont think this is correct, "requestIdleCallback will schedule work when there is free time at the end of a frame, or when the user is inactive" .. so this may get triggered when the browser thinks there's some free time not only because of user idlingDownturn
C
2

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();
Charron answered 11/7, 2020 at 10:8 Comment(1)
Providing an example on how to use it would have been nice.Charmaincharmaine
R
1

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.

Rhythmandblues answered 20/3, 2009 at 19:47 Comment(0)
R
1

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();
}]);
Rudd answered 23/10, 2015 at 17:20 Comment(0)
F
1

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 )
}
Firstnighter answered 25/9, 2018 at 12:45 Comment(0)
C
0

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.

Catha answered 20/3, 2009 at 19:20 Comment(0)
E
0

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);
Emma answered 20/3, 2009 at 21:4 Comment(3)
I wonder why so many downvotes on this answer. As far as I can see, it did answer the question asked, and is factually correct. Only didn't go on to roll out elaborate code examples.Region
Now it's possible to call a javascript function when "there is free time at the end of a frame, or when the user is inactive. " developers.google.com/web/updates/2015/08/…Muniment
setTimeout 0 is probably more correct to calculate in avarage how full the buffer is by take the time between FILOOutstanding
B
0

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>
Bonaire answered 17/5, 2013 at 21:9 Comment(0)
F
0

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

});
Flatwise answered 16/7, 2014 at 14:8 Comment(0)
S
0

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>
Satin answered 2/10, 2018 at 13:16 Comment(0)
C
0

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

Coccidioidomycosis answered 25/10, 2018 at 0:22 Comment(0)
P
0

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.

Punjabi answered 4/5, 2019 at 14:49 Comment(0)
D
0

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)
Downturn answered 20/2 at 4:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.