520

Is it possible to detect "idle" time in JavaScript?
My primary use case probably would be to pre-fetch or preload content.

Idle time: Period of user inactivity or without any CPU usage

Ramratan Gupta
  • 964
  • 2
  • 13
  • 32
Cherian
  • 17,911
  • 12
  • 53
  • 69
  • 7
    How do you define IDLE TIME? – Itay Moav -Malimovka Mar 20 '09 at 19:17
  • 4
    check https://github.com/kidh0/jquery.idle – apneadiving Oct 14 '14 at 08:28
  • 93
    Almost 50% of the answers here have used jQuery for such a simple task for pure Javascript. It's beyond daft, especially as the OP wanted Javascript. Ask for Strawberry ice-cream but get a semi-detached barn instead. – TheCarver Jul 20 '16 at 11:30
  • 5
    thats StackOverflow for you – robertmain Mar 08 '18 at 18:08
  • 5
    @TheCarver -- Having to install a jQuery library for something so simple is indeed ridiculous, and it lengthens rendering time and raises energy usage. People should scroll down a bit further. There is an almost copy-paste solution in vanilla Javascript as well. – Frank Conijn May 02 '18 at 09:23
  • 3
    @TheCarver I mean the original question was asked almost a decade ago. What do you expect. A decade is eternity in terms of web dev landscape changes. You simply couldn't live without jQuery back then. – xji Dec 13 '18 at 18:00
  • 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. – Ryan Walker Nov 07 '19 at 22:19
  • @xji Javascript has been around way longer than JQuery, I believe JQuery started the "lets find a package to do x" instead of "How can I use my programming language to do x" – Tim Davis May 15 '20 at 17:49
  • @TimDavis Sure. That's the point isn't it. jQuery was in vogue 10 years ago but is nowhere to be seen nowadays, while Javascript the language is still here. I think my comment exactly meant something to this effect, though of course it might have been a bit unnecessary in hindsight. – xji May 15 '20 at 21:18

37 Answers37

465

Here is a simple script using JQuery that handles mousemove and keypress events. If the time expires, the page reload.

<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>   
nikib3ro
  • 19,427
  • 21
  • 113
  • 173
freddoo
  • 6,370
  • 2
  • 25
  • 37
  • 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) – Jesse Roper Apr 05 '13 at 17:30
  • Agreed. Very simple and effective implementation. Also allows for quick addition of extra interval based function calls. Perfect for an auto logout script with notification – The Thirsty Ape Apr 18 '13 at 20:55
  • 10
    @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. – Felix Kling Nov 17 '13 at 17:51
  • 2
    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; });` – Paul Annekov Jan 09 '14 at 12:01
  • 7
    You could simply use `idleTime++;` instead of `idleTime = idleTime + 1;` – Mike Causer Jan 16 '14 at 04:30
  • 7
    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... – Sander Jan 21 '14 at 13:53
  • 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?) – Kokodoko Mar 25 '14 at 10:31
  • 2
    @Sander About the performance - it costs little to none to assign `0` to `idleTime`. Hey, I even made jsperf test for you: http://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`. – klh May 09 '14 at 03:28
  • 5
    @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? – klh May 09 '14 at 03:32
  • Will this interfere with existing mousemove event handlers? – quillbreaker Oct 22 '14 at 16:17
  • 1
    @quillbreaker: No, this will attach a function to the mousemove-event. – Jonathan Jan 22 '15 at 08:42
  • What happens when multiple tabs are open and user forgot to interact with one of them and lies in background? – Alexander Suraphel Mar 29 '18 at 14:16
  • All of these great contributions and not one answer accepted? Shame. – Gray Spectrum May 15 '18 at 20:30
  • still not sure how this helps with cpu usage. Please advise – qwerty_igor May 15 '19 at 23:06
  • 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? – Tiago Oct 07 '20 at 14:47
422

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 init 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 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. window.addEventListener('scroll', resetTimer, true), the third argument tells the listener to catch event during capture phase instead of bubble phase.

equiman
  • 6,624
  • 2
  • 40
  • 44
  • 68
    I like the plain javascript approach much better. – Manatax Oct 08 '15 at 22:35
  • 4
    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. – Josh Sutterfield May 04 '16 at 16:05
  • When it is called in front end? – mpsbhat Jun 17 '16 at 04:58
  • 1
    @mpsbhat all this is the function... you define when call it using "onload", an "anonymous function" or with an "event". – equiman Jun 18 '16 at 23:40
  • Like this https://jsfiddle.net/mpsbhat/6b6mja5t/ ?? In the fiddle, it will call `onload` of body i hope. – mpsbhat Jun 19 '16 at 16:09
  • 2
    @mpsbhat 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(); };` – equiman Jun 22 '16 at 15:03
  • 2
    Yeah... Working. https://jsfiddle.net/mpsbhat/6b6mja5t/1/. Thanks @equiman – mpsbhat Jun 23 '16 at 04:09
  • 6
    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. – MartinWebb Nov 26 '16 at 13:50
  • 1
    @MartinWebb maybe, but i think is more complex using a global variable than calling the function in any "document" or "window". – equiman Nov 30 '16 at 20:02
  • 2
    Simple / elegant solution I just updated and added the timeout as argument like: ```var inactivityTime = function (timeout) {``` so we have control of it form outside – Ignacio Vazquez Jan 31 '18 at 13:16
  • 1
    Very simple solution with javascript – UI_Dev Feb 05 '19 at 04:25
  • 2
    I didn't want this to overwrite any existing event handlers so I used: `window.addEventListener("load", resetTimer, true);["mousedown", "mousemove", "keypress", "scroll", "touchstart"].forEach(function(name) { document.addEventListener(name, resetTimer, true); });` inspired by the answer from @JackTheKnife – Charles L. Feb 19 '19 at 21:43
  • Excellent @CharlesL. I already edited the answer adding this alternative. – equiman Feb 20 '19 at 19:18
  • I think you delete your first answer and use addEventListener exclusively. Using onXXX is only going to bring back users asking why it broke something else or something else broke it. – gman Mar 05 '19 at 03:43
  • @CharlesL. just curious why you don't listen for window.onclick? Is it because this would be redundant with mousedown? – Pancake Jan 30 '20 at 23:55
  • 1
    @Pancake It's been too long for me to remember for sure, but my guess would be the same as yours, it seems redundant. – Charles L. Feb 01 '20 at 00:20
  • This is the solution that I have implmented on a control panel for my employeer. Still I have two issues – davidhartman00 Mar 16 '20 at 20:41
  • I can't seem to get this to work if the mouse cursor is in the window. Using Chrome v85. – drichardson Sep 24 '20 at 13:43
  • @drichardson you need to add the `onmousemove` event. – equiman Sep 24 '20 at 17:54
  • 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? – Tiago Oct 07 '20 at 14:48
  • @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`. – equiman Oct 07 '20 at 14:58
  • @Tiago 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. – equiman Oct 08 '20 at 04:17
  • 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? – veritas Oct 22 '20 at 14:05
  • @veritas 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. – equiman Dec 09 '20 at 18:35
  • @equiman 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://stackoverflow.com/a/12522580/1315956 – im1dermike May 17 '21 at 14:49
92

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

Frank Conijn
  • 2,627
  • 19
  • 28
  • 5
    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 answer](https://stackoverflow.com/a/32954565/4997172) – DaxChen Nov 23 '17 at 02:45
  • @DaxChen -- Doesn't `document.onscroll` have the same problem, not firing if the scrolling is inside a scrollable child? – Frank Conijn May 01 '18 at 22:48
  • 2
    Yes, the point I was saying is to use`addEventListener` instead of `onscroll`. – DaxChen May 02 '18 at 01:15
  • @DaxChen -- 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. – Frank Conijn May 02 '18 at 09:13
  • 1
    I'll update my answer with this important information, to avid other copy and paste my mistake. Thanks @DaxChen and Frank – equiman Oct 18 '19 at 11:56
  • 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? – BluePie Sep 03 '20 at 06:32
  • 2
    btw [onkeypress is depricated](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onkeypress) use [onkeydown](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onkeydown) – BluePie Sep 03 '20 at 08:38
  • @BluePie - 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. – Frank Conijn Sep 05 '20 at 05:52
  • 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? – Tiago Oct 07 '20 at 14:48
  • @Tiago — 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. – Frank Conijn Dec 02 '20 at 23:26
34

I have created a small lib that does this a year ago:

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: PM> Install-Package Idle.js

Shawn Mclean
  • 53,945
  • 92
  • 265
  • 401
32

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

})
Simon Perepelitsa
  • 19,436
  • 8
  • 53
  • 72
Peter J
  • 55,560
  • 7
  • 36
  • 45
  • 9
    This solution doesn't consider keyboard events. – Daniel Silveira Mar 01 '10 at 12:43
  • 7
    Never pass `setInterval` a string! Just give a function as a variable! – Eric Oct 07 '11 at 14:08
  • 1
    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. – jfriend00 Jan 26 '13 at 05:22
  • Thanks, I was not aware of the need to NEVER pass strings to setInterval. Updated my answer. – Peter J Feb 01 '13 at 15:33
25

Similar to Iconic's solution above (with jQuery custom event)...

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

//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 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; // min 100ms
    if (timeout > 300000) timeout = 300000; // max 5 minutes

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

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

}(window.jQuery, window.jQuery(window)))
Tracker1
  • 17,841
  • 10
  • 75
  • 104
21

You can do it more elegantly with underscore and jquery-

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

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.

johnnyRose
  • 6,243
  • 16
  • 40
  • 58
  • 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 case – Carmela Jun 09 '16 at 07:48
  • @Carmela: I'm not sure why I'm just seeing this; I must have missed it. Thanks, I removed the `onclick` assignment since it's probably not necessary in addition to `onmousemove`, but obviously any of these events which are triggered programmatically will continue to reset `idleCounter`. I'm not sure why you'd simulate user interaction instead of just calling a function, but if that's something you need to do for some reason, this answer obviously won't work for you, nor will most of the other answers I've looked at on this question. – johnnyRose Sep 22 '17 at 14:17
18

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/

Sarsaparilla
  • 4,860
  • 1
  • 25
  • 17
  • 1
    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? – Oranges13 Aug 12 '15 at 14:21
  • 1
    You can call this any time, even before document is ready. You pass a function 'callback' that will be called when the timer expires. – Sarsaparilla Aug 13 '15 at 00:42
  • 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". – Ifedi Okonkwo Dec 09 '15 at 06:53
  • 1
    The `$(startTimer)` is equivalent to `$(document).ready(startTimer)`, ensures that the DOM is ready before you hook the mousemove and keypress events. – Sarsaparilla Dec 09 '15 at 18:02
  • 2
    +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. – goat Feb 01 '17 at 23:28
  • 1
    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. – aFerrer Mar 14 '19 at 20:01
16

I know it a relatively old question, but 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 Info: see this for back-end event tracking Leads browserload)

Community
  • 1
  • 1
DDan
  • 7,436
  • 4
  • 29
  • 47
  • 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? – Mubasher Jul 13 '16 at 10:30
  • [Here](https://github.com/kidh0/jquery.idle#user-content-events) it says: "idle:stop": will stop and remove user tracking – DDan Jul 14 '16 at 00:51
  • I already have read but could not figure out how to use this, Could you help me ? – Mubasher Jul 14 '16 at 08:07
  • 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: https://jsfiddle.net/f238hchm/12/ – DDan Jul 14 '16 at 08:49
  • No I do not fire once, keepTracking should be true, but on navigation to other page I want to stop this – Mubasher Jul 14 '16 at 10:00
  • What to do If I want same functionality for 20 minutes? – Shreya Rawal Dec 14 '16 at 07:34
  • Since I am passing the time in idle like this ... idle: 20*60*1000 ..which is not working. – Shreya Rawal Dec 14 '16 at 07:34
  • @ShreyaRawal try like this: idle: (20*60*1000). Use parentheses. The expression yields to int and it will work. See example: https://jsfiddle.net/ddan/f238hchm/24/ (at the end of the JS snippet) – DDan Dec 14 '16 at 10:22
  • It is not working for values more than 5 minutes @DDan – Shreya Rawal Dec 14 '16 at 12:27
  • I just tried your link https://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? – DDan Dec 15 '16 at 05:51
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.

tvanfosson
  • 490,224
  • 93
  • 683
  • 780
  • 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. – Dave Swersky Mar 20 '09 at 19:47
  • 1
    I took the liberty of implementing your idea in jQuery. – Peter J Mar 20 '09 at 21:14
11

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

I might release it as an npm package once I get some feedback.

Capi Etheriel
  • 3,115
  • 22
  • 41
10

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.

rajsite
  • 820
  • 1
  • 8
  • 10
  • And the leave the other browsers alone? – Frank Conijn Nov 12 '18 at 04:00
  • 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? –  Jul 09 '19 at 13:49
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";
    }
}
Rohit Sharma
  • 3,035
  • 2
  • 16
  • 29
vijay
  • 87
  • 1
  • 1
5
<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!! donot forgot to include jquery library in your file!

Anders Marzi Tornblad
  • 17,122
  • 9
  • 50
  • 62
Sunil Garg
  • 146
  • 2
  • 6
5

(Partially inspired by the good core logic of Equiman earlier in this thread.)

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

Fom
  • 294
  • 5
  • 12
4

The problem with all these solutions, although correct, 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 & 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 & JavaScript timer are reset. Simple & 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.

Charles Robertson
  • 1,546
  • 13
  • 19
  • 1
    Yep, that's why I'm only doing this after getting rid of server side sessions and loading everything from html files. – Dan Parker Feb 22 '16 at 23:02
4

Pure JavaScript with 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);
  });

}());
JackTheKnife
  • 2,698
  • 4
  • 38
  • 82
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.

Hope this helps :)

  • Is there really a need for a delay, isn’t triggering a custom event from a custom event handler enough? – Hibou57 Sep 04 '17 at 23:24
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);
    });
2

Here is the best solution I have found: http://css-tricks.com/snippets/jquery/fire-event-when-user-is-idle/

Here is the JS:

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)
Casey
  • 1,562
  • 2
  • 20
  • 32
2

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);
}
Abhishek
  • 321
  • 5
  • 19
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, 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);    
        },
MartinWebb
  • 1,748
  • 1
  • 11
  • 12
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();
2

Just a few thoughts, an avenue or two to explore.

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.

Again, just some thoughts... Hope it helps.

Mark Rullo
  • 89
  • 9
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();
}]);
steampowered
  • 10,819
  • 10
  • 63
  • 94
1

Debounce actually a great idea! Here version for jQuery free projects:

const derivedLogout = createDerivedLogout(30);
derivedLogout(); // it could happen that user 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 ) 
}
Gleb Dolzikov
  • 556
  • 5
  • 10
1

As simple as it can get, detect when 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 60s in this case.

function idleFuntion()
{
   console.log('user is idle');
}
lucss
  • 49
  • 3
1

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.

Cajotafer
  • 56
  • 5
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.

Walden Leverich
  • 4,190
  • 2
  • 19
  • 29
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>
0

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

});
Denys Wessels
  • 16,209
  • 14
  • 72
  • 115
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>
Joseph Crispell
  • 337
  • 1
  • 7
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).

Meglio
  • 1,263
  • 1
  • 14
  • 26
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 activity status (online / away), so we have a sense of active users in our system.

My class is based on Equiman's & Frank Conijn's answers.

TechWisdom
  • 2,750
  • 3
  • 27
  • 35
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 millis) and start your preloading.

Eric Wendelin
  • 39,122
  • 8
  • 59
  • 87
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);
Powerlord
  • 82,184
  • 16
  • 119
  • 164
  • 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. – Ifedi Okonkwo Dec 09 '15 at 07:03
  • 1
    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. " https://developers.google.com/web/updates/2015/08/using-requestidlecallback – Max Apr 21 '17 at 08:25
  • setTimeout 0 is probably more correct to calculate in avarage how full the buffer is by take the time between FILO –  Jul 09 '19 at 14:02