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

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

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


当前回答

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

其他回答

是否有可能每10秒运行一个函数,并检查一个“计数器”变量?如果可能的话,你可以在页面上进行鼠标悬停,不是吗?

如果是,使用鼠标悬停事件重置“counter”变量。如果函数被调用,并且计数器在您预先确定的范围之上,那么执行您的操作。

你可以用Underscore.js和jQuery更优雅地做到这一点:

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

下面是一个使用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>

所有前面的答案都有一个始终活动的鼠标移动处理程序。如果处理程序是jQuery,那么jQuery执行的额外处理可以加起来。特别是当用户使用游戏鼠标时,每秒可能发生多达500个事件。

这个解决方案避免处理每一个鼠标移动事件。这导致一个小的时间误差,但你可以调整你的需要。

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/

我使用这种方法,因为您不需要在事件触发时不断重置时间。相反,我们只记录时间,这将生成空闲的起始点。

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