- 人工智能
- AI Agent
- MCP 服务
- 工具调用
- GUI 自动化
- 语音
- 数字人
- 交互助手
【免费下载链接】airi
💖🧸 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器,目标是接近 Neuro-sama 的高度;支持实时语音聊天、Minecraft 和 Factorio 游玩,支持 Web / macOS / Windows。
useIdle是 VueUse 中用于跟踪用户是否处于不活跃状态的响应式组合式函数(composable),它通过监听一组浏览器事件自动推断"空闲"与"活跃"的切换,并暴露idle、lastActive与reset三个可编程接口。本篇指南以 airi 仓库内收录的 useIdle 参考文档 为主体,完整讲解其调用方式、选项参数与类型契约,并结合 airi 中stage-ui-three的 VRM 空闲动画与stage-tamagotchi桌面端的空闲/休眠实践,说明在 AI 伴侣类桌面应用中如何利用空闲状态驱动资源节能与行为切换。读完本文,你将掌握useIdle的完整 API、自定义事件集的正确姿势,以及把它接入 Vue 3 / Nuxt 项目的可运行方案。
useIdle 的功能定位:用户活跃度传感器
useIdle属于 VueUse 功能分类中的Sensors(传感器)类别。在 airi 仓库的 vueuse-functions 技能清单 中,它被描述为 "Tracks whether the user is being inactive"(跟踪用户是否处于不活跃状态),与useMouse、usePageLeave、useNetwork等传感器函数并列,调用规则为AUTO,即只要场景适用就应优先考虑使用。
它解决的问题很直接:在一段时间内用户没有任何操作时,应用需要做出响应。典型场景包括:
- 暂停不必要的动画循环、降低渲染帧率以节约 CPU/GPU 与电量;
- 停止自动轮询、后台拉流等网络行为;
- 触发"离开"状态提示、锁屏或节能模式;
- 在 AI 伴侣类应用中,切换到角色"空闲待机"动画或停止语音等待逻辑。
在 airi 中,"空闲"是一个被多处使用的概念(详见下文"仓库内的空闲实践"),而useIdle正是从 DOM 事件层面提供这一信号的标准工具。
基础用法:设定超时阈值并读取状态
useIdle的签名很简洁:第一个参数是超时时间(毫秒),超过该时长未检测到用户活动即认为进入空闲。默认超时时间为1 分钟。
import { useIdle } from '@vueuse/core' const { idle, lastActive } = useIdle(5 * 60 * 1000) // 5 min console.log(idle.value) // true or false返回的两个核心响应式值:
| 返回值 | 类型 | 含义 |
|---|---|---|
idle | ShallowRef<boolean> | 用户当前是否处于空闲状态 |
lastActive | ShallowRef<number> | 最近一次检测到用户活动的时间戳(毫秒) |
idle的初始值由initialState选项控制(默认false,即初始视为活跃),lastActive则记录最近一次触发活跃事件的时间。两者都是浅层 ref(ShallowRef),读取时使用.value。
需要说明的是:airi 仓库的 pnpm-lock.yaml 锁定的@vueuse/core版本为 14.4.0(配合 Vue 3.5.x),上述用法在该版本及多数现代 VueUse 版本中一致;若项目中使用的是更早期版本,请以实际安装版本的类型声明为准。
编程式重置:reset 与活跃回调的配合
文档提供了一个非常实用的模式:当用户从空闲状态恢复活动时,可以通过reset()立即重启空闲计时器。它与useCounter配合的完整示例:
import { useCounter, useIdle } from '@vueuse/core' import { watch } from 'vue' const { inc, count } = useCounter() const { idle, lastActive, reset } = useIdle(5 * 60 * 1000) // 5 min watch(idle, (idleValue) => { if (idleValue) { inc() console.log(`Triggered ${count.value} times`) reset() // restarts the idle timer. Does not change lastActive value } })这里有两个值得注意的语义细节:
reset()只重启计时器,不修改lastActive。也就是说,它仅把"下一次判定空闲的倒计时"归零,而最近活跃时间戳保持原值。如果你需要同步刷新lastActive,应配合用户活动事件自行更新。watch监听的是idle从false→true的切换。利用 Vue 的watch默认行为,在进入空闲时执行副作用;若需要在退出空闲(true→false)时也做处理,需要把watch(idle, ...)改为传入 getter 或使用watch的回调参数判断前后值。
该模式非常适合"空闲计时 + 行为计数/日志埋点"的场景:例如统计用户离开桌面端应用的次数,每次进入空闲时记录一次并立即重置,以便精确度量单次会话中的活跃片段时间。
组件式用法:UseIdle
除了组合式函数调用,VueUse 还提供了对应的无渲染组件形式UseIdle,适合在模板中直接使用作用域插槽(scoped slot):
<template> <UseIdle v-slot="{ idle }" :timeout="5 * 60 * 1000"> Is Idle: {{ idle }} </UseIdle> </template>组件的timeoutprop 对应useIdle的第一个参数,插槽中可解构出idle等返回值。这种方式在需要把空闲状态直接映射到模板渲染(例如显示"离开中"气泡、隐藏某些交互元素)时更为直观,无需在<script setup>中额外声明变量。
选项参数详解:UseIdleOptions
useIdle的第二个参数是选项对象,完整类型如下:
export interface UseIdleOptions extends ConfigurableWindow, ConfigurableEventFilter { /** * Event names that listen to for detected user activity * * @default ['mousemove', 'mousedown', 'resize', 'keydown', 'touchstart', 'wheel'] */ events?: WindowEventName[] /** * Listen for document visibility change * * @default true */ listenForVisibilityChange?: boolean /** * Initial state of the ref idle * * @default false */ initialState?: boolean }三个选项各自的作用:
| 选项 | 默认值 | 说明 |
|---|---|---|
events | ['mousemove', 'mousedown', 'resize', 'keydown', 'touchstart', 'wheel'] | 用于判定"用户活跃"的监听事件名列表。默认覆盖鼠标移动/按下、窗口缩放、键盘输入、触摸与滚轮 |
listenForVisibilityChange | true | 是否监听document.visibilitychange。开启后,页面重新可见(visibilitychange回到visible)也会被计为一次用户活动 |
initialState | false | idleref 的初始状态。默认认为初始是活跃的 |
此外,UseIdleOptions继承了两个基接口:
ConfigurableWindow:允许传入自定义window对象(通常用于测试或非浏览器环境模拟),不传则回退到全局window。从默认事件集可以推断,useIdle实际是在window上挂载mousemove、mousedown、resize、keydown、touchstart、wheel监听器,并在任一事件触发时刷新计时。ConfigurableEventFilter:允许通过eventFilter对事件做节流/防抖等过滤处理(如与throttleFilter、debounceFilter组合),控制"活跃事件 → 重置计时"的触发频率,避免高频事件(如mousemove)带来不必要的开销。
自定义events的示例——例如只关注键盘与鼠标按键,忽略持续移动造成的"误活跃":
const { idle } = useIdle(3 * 60 * 1000, { events: ['mousedown', 'keydown', 'touchstart'], listenForVisibilityChange: true, })关闭listenForVisibilityChange的场景通常是:应用在多窗口环境(如 airi 的 Electron 多窗口架构)中,不希望"标签页/窗口重新获得焦点"这一行为被误判为用户亲自操作。
返回值与类型契约:UseIdleReturn 与 Stoppable
export interface UseIdleReturn extends Stoppable { idle: ShallowRef<boolean> lastActive: ShallowRef<number> reset: () => void } /** * Tracks whether the user is being inactive. * * @param timeout default to 1 minute * @param options IdleOptions */ export declare function useIdle( timeout?: number, options?: UseIdleOptions, ): UseIdleReturnUseIdleReturn继承自 VueUse 的Stoppable契约。Stoppable是 VueUse 组合式函数家族中"可停止"能力的标准形态,通常包含isActive/isPending等状态与stop()方法,用于在组件卸载或不再需要时解除事件监听、释放资源。可以推断:useIdle内部通过tryOnScopeDispose或显式返回的stop()来在作用域销毁时清理挂在window上的事件监听器,从而避免内存泄漏——这也是useIdle能够放心在任意组件中反复调用的前提。
一句话总结 API 契约:入参一个超时时间与一组选项,出参是空闲标志、最近活跃时间戳与一个只重置计时不清除记录的重置函数,并且整体可以随时停止。
从源码结构看实现原理
虽然仓库内没有内置 VueUse 的源码,但根据类型声明与默认值可以合理推断其实现机制:
- 计时模型:内部维护一个
setTimeout(时长即timeout),超时后将idle置为true;一旦监听到任意活跃事件,立即清除旧定时器、刷新lastActive为当前时间戳并重新计时,同时把idle置回false。 - 事件监听集合:默认的 6 个事件覆盖了桌面与触屏的主要输入通道;
touchstart的存在说明它天然适配移动端。 - 可见性兜底:
listenForVisibilityChange: true时额外监听document.visibilitychange,当页面从 hidden 回到 visible 时视为活跃——这保证了"切走再切回"不会让应用误以为用户仍在空闲。 - 初始状态:
initialState直接决定idle的初值;默认false意味着从组件挂载到首次超时之间,应用按"用户在场"处理。
这些推断与 VueUse 官方文档对useIdle的说明一致,读者可在实际接入后通过 DevTools 观察lastActive的时间戳变化来验证。
仓库内的空闲实践:airi 如何围绕"空闲"做文章
虽然 airi 仓库当前并没有直接调用useIdle的现成代码,但"空闲"这一状态在项目中被多处显式建模,可以作为接入useIdle的绝佳结合点:
VRM 角色的空闲待机动画与眼部扫视
在 VRMModel.vue 中,角色模型支持通过idleAnimation属性指定空闲时的待机动画(.vrma文件),并在 animation.ts 中提供了两个与空闲行为直接相关的组合式函数:
useBlink(animation.ts#L92-L152):模拟自然的眨眼周期,MIN_BLINK_INTERVAL(1 秒)与MAX_BLINK_INTERVAL(6 秒)之间随机决定下一次眨眼时机,用正弦曲线驱动blink表情的 morph 值。useIdleEyeSaccades(animation.ts#L154-L264):在用户"空闲"时模拟眼球扫视(saccade),让角色的视线在注视目标附近随机游走(randFloat(-0.25, 0.25)),使其不至于死盯着一点,产生"活着的赛博生命"观感。
从代码注释"simulate idle eye saccades"与"idleAnimation: animation src for model"可以看出,airi 的空闲概念是驱动角色表现层行为的关键信号。一个自然的结合方式是:用useIdle(5 * 60 * 1000)判定用户离开,空闲时切换到低功耗渲染模式或让角色的待机动画接管,活跃时恢复注视交互——这正是useIdle输出信号与仓库现有idleAnimation/useIdleEyeSaccades能力的天然衔接点。
桌面端的多窗口空闲与电源事件
airi 的桌面端(apps/stage-tamagotchi,基于 Electron)在 powerMonitor.ts 中监听系统级的suspend、resume、lock-screen、unlock-screen事件,并通过事件总线广播给各窗口。这是操作系统层面的"空闲"信号;而useIdle提供的是 DOM 事件层面的"用户无操作"信号,两者互补:
- 系统休眠/锁屏 → 由
powerMonitor处理; - 窗口可见但用户长时间无操作 → 由
useIdle处理。
Controls Island 的空闲相位
在桌面端的 use-controls-island-placement.ts 中,悬浮控制岛的移动动画定义了ControlsIslandMotionPhase = 'idle' | 'leaving' | 'entering' | 'arriving'相位模型,idle表示岛停留在某个角落的静止状态。虽然这是动画状态机的命名,但也印证了项目代码中统一的"idle 即静止/无动作"语义,与useIdle的判定语义一致。
使用建议与边界
- 超时时间按场景选取:默认 1 分钟对"是否离开"的判断偏激进;对于 airi 这类常驻桌面的 AI 伴侣,建议使用 5~10 分钟(如
5 * 60 * 1000),避免用户阅读长文本时被频繁误判。 - 合理收窄事件集:默认事件包含
mousemove与wheel,在视频播放等场景中会造成"持续活跃";若只想感知真实交互,可自定义events: ['mousedown', 'keydown', 'touchstart']。 - 配合
Stoppable的停止能力:在组件卸载或应用进入系统休眠时调用stop()解除监听,避免 Electron 渲染进程长驻导致的事件泄漏。 - 区分"窗口不可见"与"用户空闲":
listenForVisibilityChange只解决页面重新可见时的刷新;窗口最小化期间的判定仍依赖事件监听,必要时与powerMonitor的锁屏/休眠事件配合形成完整的活跃度画像。 - SSR 注意:
useIdle依赖window事件,服务端渲染(Nuxt SSR)期间不会真正监听;应在客户端渲染阶段使用,或通过ConfigurableWindow注入测试用 window 对象。
结语
useIdle以极小的 API 面(一个超时参数、三个选项、三个返回值)覆盖了"用户是否在场"这一高频需求,其默认事件集与可见性兜底使其在 Web 与 Electron 桌面端都能开箱即用。在 airi 中,它可以与既有的 VRM 空闲动画、useIdleEyeSaccades、电源事件服务无缝组合,构成从"系统级"到"DOM 级"的完整空闲检测链路。掌握了useIdle的参数语义与重置/停止契约,你便能在任何 Vue 3 / Nuxt 项目中快速落地空闲驱动的节能与行为切换逻辑。
- 人工智能
- AI Agent
- MCP 服务
- 工具调用
- GUI 自动化
- 语音
- 数字人
- 交互助手
【免费下载链接】airi
💖🧸 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器,目标是接近 Neuro-sama 的高度;支持实时语音聊天、Minecraft 和 Factorio 游玩,支持 Web / macOS / Windows。
相关推荐
如何安装komorebi平铺窗口管理器:Scoop、WinGet与源码编译3种方式完整指南
如何安装komorebi平铺窗口管理器:Scoop、WinGet与源码编译3种方式完整指南 komorebi 是一款专为 Windows 10/11 打造的开源
桌面应用VueUse useIdle 完全指南:追踪用户空闲状态与自动重置计时器
VueUse useIdle 完全指南:追踪用户空闲状态与自动重置计时器 useIdle 是 VueUse Sensors(传感器)分类下的核心组合式函数,用于
前端react-use 的 useIdle Hook 详解:基于交互事件实时追踪用户空闲状态
react use 的 useIdle Hook 详解:基于交互事件实时追踪用户空闲状态 导读 useIdle 是 react use( package.jso
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考