File size: 2,589 Bytes
793e953 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | /**
* useIdleTimer Hook
*
* 检测用户空闲状态(默认 5 分钟无操作)。
* 监听鼠标移动、键盘按下、触摸等事件,空闲时触发 onIdle 回调。
*
* 用法:
* const { isIdle, resetTimer } = useIdleTimer({
* timeout: 5 * 60 * 1000, // 5 分钟
* onIdle: () => console.log('用户空闲'),
* onActive: () => console.log('用户恢复活动'),
* })
*/
import { useState, useEffect, useCallback, useRef } from 'react'
interface UseIdleTimerOptions {
/** 空闲超时时间(ms),默认 5 分钟 */
timeout?: number
/** 空闲时触发 */
onIdle?: () => void
/** 从空闲恢复活动时触发 */
onActive?: () => void
/** 是否启用,默认 true */
enabled?: boolean
}
interface UseIdleTimerReturn {
/** 当前是否空闲 */
isIdle: boolean
/** 手动重置空闲计时器 */
resetTimer: () => void
}
const EVENTS = ['mousemove', 'mousedown', 'keydown', 'touchstart', 'scroll', 'wheel'] as const
export function useIdleTimer(options: UseIdleTimerOptions = {}): UseIdleTimerReturn {
const { timeout = 5 * 60 * 1000, onIdle, onActive, enabled = true } = options
const [isIdle, setIsIdle] = useState(false)
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const isIdleRef = useRef(false)
const onIdleRef = useRef(onIdle)
const onActiveRef = useRef(onActive)
onIdleRef.current = onIdle
onActiveRef.current = onActive
const clearTimer = useCallback(() => {
if (timerRef.current) {
clearTimeout(timerRef.current)
timerRef.current = null
}
}, [])
const startTimer = useCallback(() => {
clearTimer()
if (!enabled) return
timerRef.current = setTimeout(() => {
isIdleRef.current = true
setIsIdle(true)
onIdleRef.current?.()
}, timeout)
}, [timeout, enabled, clearTimer])
const resetTimer = useCallback(() => {
if (!enabled) return
if (isIdleRef.current) {
isIdleRef.current = false
setIsIdle(false)
onActiveRef.current?.()
}
startTimer()
}, [enabled, startTimer])
useEffect(() => {
if (!enabled) {
clearTimer()
return
}
startTimer()
const handleActivity = () => {
resetTimer()
}
EVENTS.forEach((event) => {
window.addEventListener(event, handleActivity, { passive: true })
})
return () => {
clearTimer()
EVENTS.forEach((event) => {
window.removeEventListener(event, handleActivity)
})
}
}, [enabled, startTimer, resetTimer, clearTimer])
return { isIdle, resetTimer }
}
|