是否有可能在JavaScript中检测“空闲”时间?

我的主要用例可能是预取或预加载内容。

我将空闲时间定义为用户不活动或没有任何CPU使用的时间段


当前回答

纯JavaScript,通过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);
  });

}());

其他回答

改进Equiman(原始)的答案:

function idleLogout() {
    var t;
    window.onload = resetTimer;
    window.onmousemove = resetTimer;
    window.onmousedown = resetTimer;  // catches touchscreen presses as well      
    window.ontouchstart = resetTimer; // catches touchscreen swipes as well      
    window.ontouchmove = resetTimer;  // required by some devices 
    window.onclick = resetTimer;      // catches touchpad clicks as well
    window.onkeydown = resetTimer;   
    window.addEventListener('scroll', resetTimer, true); // improved; see comments

    function yourFunction() {
        // your function for too long inactivity goes here
        // e.g. window.location.href = 'logout.php';
    }

    function resetTimer() {
        clearTimeout(t);
        t = setTimeout(yourFunction, 10000);  // time is in milliseconds
    }
}
idleLogout();

除了活动检测方面的改进,以及从文档到窗口的更改之外,这个脚本实际上调用了函数,而不是让它闲置着。

它不会直接捕获零CPU使用情况,但这是不可能的,因为执行函数会导致CPU使用。用户不活动最终导致CPU使用率为零,因此它间接地捕捉到CPU使用率为零。

下面是一个使用jQuery处理鼠标移动和按键事件的简单脚本。 如果时间过期,页面将重新加载。

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

我在这里提出的实现方法与其他答案在以下方面有所不同:

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

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

示例用法(例如文件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);
    });
});

示例输出(节选):

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

上面的输出是当我切换到另一个选项卡并在那里做一些活动时产生的。可以看到,计时器有时会延迟(我想是因为在后台选项卡中,计时器以精确的速率被触发不是优先级)。但是空闲计时器仍然以正确的+/- 1秒的间隔触发。在这种情况下,1秒是空闲计时器的精度(通过idle .js中的IDLE_TIMER_RATE_SECONDS常量配置)。

类似于Peter J的解决方案(使用jQuery自定义事件)…

// Use the jquery-idle-detect.js script below
$(window).on('idle:start', function() {
  // Start your prefetch, etc. here...
});

$(window).on('idle:stop', function() {
  // Stop your prefetch, etc. here...
});

文件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)))

所有这些解决方案的问题,尽管是正确的,但在使用PHP、. net或在应用程序中考虑会话超时值集时,它们是不切实际的。为ColdFusion开发人员提供cfc文件。

上述解决方案设置的时间需要与服务器端会话超时同步。如果两者不同步,您可能会遇到一些问题,这些问题只会让用户感到沮丧和困惑。

例如,服务器端会话超时可能被设置为60分钟,但用户可能认为他/她是安全的,因为JavaScript空闲时间捕获增加了用户在单个页面上花费的总时间。用户可能花了很多时间填写一个很长的表单,然后去提交。会话超时可能在处理表单提交之前开始。

我倾向于只给用户180分钟,然后使用JavaScript自动注销用户。实际上,使用上面的一些代码来创建一个简单的计时器,但是没有捕获鼠标事件部分。

通过这种方式,我的客户端和服务器端时间完全同步。如果您在UI中向用户显示时间,就不会出现混乱。每次在CMS中访问一个新页面时,服务器端会话和JavaScript计时器都会被重置。简单而优雅。如果一个用户在一个页面上停留超过180分钟,首先我认为这个页面有问题。