在 Angular 与 Electron 混合架构下,空闲屏保不是单纯靠前端 setTimeout 就能稳妥搞定的需求。Electron 提供了独立于浏览器的系统事件监听能力,而 Angular 擅长用服务和组件管理界面状态,两者配合才能覆盖窗口失焦、系统休眠等边界情况。

为什么要在主进程做空闲检测
很多团队一开始会把闲置判断写在 Angular 的渲染进程里,监听 document 的 mousemove 和 keydown。这种做法在单页面内可行,但一旦用户把 Electron 窗口最小化,或者焦点切到别的桌面应用,渲染进程里的事件就不再触发,导致计时逻辑错乱。更严重的是,系统级挂起恢复后,网页里的定时器可能出现大偏差。
Electron 主进程通过 powerMonitor 和全局输入事件可以感知整个操作系统的活跃状态。把空闲时长统计放在主进程,渲染进程只负责接收“已进入空闲”或“用户已恢复”的信号,职责清晰且更可靠。主进程还能区分是鼠标移动还是键盘输入,便于后续扩展。
主进程监听与 IPC 通信
在主进程的入口文件里,我们使用 electron 的 screen、powerMonitor 以及全局快捷键模块来捕获输入。下面示例用一个简单的轮询加原生事件方式统计空闲时间,到达阈值后通过 webContents 发送消息。
const { app, BrowserWindow, ipcMain, powerMonitor } = require('electron');
let idleTime = 0;
let idleThreshold = 60; // 单位秒
let lastInput = Date.now();
// 模拟输入检测,实际可结合 robotjs 或 native 模块
setInterval(() => {
const now = Date.now();
// 此处简化:若系统未挂起且距上次输入超过1秒则累加
if (now - lastInput >= 1000) {
idleTime += 1;
} else {
idleTime = 0;
lastInput = now;
}
if (idleTime >= idleThreshold) {
const win = BrowserWindow.getAllWindows()[0];
if (win) {
win.webContents.send('idle-status', { state: 'idle' });
}
}
}, 1000);
powerMonitor.on('resume', () => {
idleTime = 0;
lastInput = Date.now();
});
ipcMain.on('user-activity', (event) => {
idleTime = 0;
lastInput = Date.now();
event.reply('idle-status', { state: 'active' });
});
上面代码用 setInterval 粗略统计,真实项目可引入更精确的输入钩子。关键在于通过 webContents.send 把状态推给渲染进程,并用 ipcMain 处理来自 Angular 的手动唤醒请求。
这种通信方式解耦了检测与展示。Angular 端不需要知道操作系统细节,只要监听 idle-status 通道即可。后续若调整阈值,只需改主进程配置,无需重新打包前端。
Angular 中的屏保服务与组件
在 Angular 侧,我们先建立一个 IdleService 来封装 IPC 监听,并暴露一个可观察的状态流。这样任何组件都能注入该服务并响应空闲变化。
import { Injectable, NgZone } from '@angular/core';
import { ipcRenderer } from 'electron';
import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class IdleService {
private idleState = new BehaviorSubject<boolean>(false);
public idle$ = this.idleState.asObservable();
constructor(private zone: NgZone) {
ipcRenderer.on('idle-status', (event, arg) => {
this.zone.run(() => {
this.idleState.next(arg.state === 'idle');
});
});
}
notifyActivity() {
ipcRenderer.send('user-activity');
}
}
IdleService 利用 NgZone 保证 Electron 回调能触发 Angular 变更检测。idle$ 流让模板或守卫订阅变得简单。notifyActivity 方法允许用户在屏保界面点击时主动退出。
接着我们写一个全屏遮罩组件,它不参与业务路由,而是以独立 overlay 形式挂载在根组件。组件模板使用固定定位覆盖整个窗口,并通过异步管道订阅空闲状态。
<div class="screensaver-overlay" *ngIf="idle$ | async"> <p>系统空闲中,点击任意位置恢复</p> <button (click)="restore()">立即恢复</button> </div>
import { Component } from '@angular/core';
import { IdleService } from './idle.service';
@Component({
selector: 'app-screensaver',
templateUrl: './screensaver.component.html'
})
export class ScreensaverComponent {
constructor(private idle: IdleService) {}
restore() {
this.idle.notifyActivity();
}
}
该组件样式应设 position: fixed; top:0; left:0; width:100%; height:100%; z-index:9999,并隐藏鼠标指针。由于它独立于业务模块,不会打断表单组件的本地状态。
把 ScreensaverComponent 直接放在 AppComponent 模板里,就能保证任何路由下都能弹出。比起在每次路由切换时判断,这种全局 overlay 更省心,也避免了重复实例化。
处理边界与用户体验
屏保出现后,如果用户移动鼠标,主进程会在下一次计时归零时发 active 状态,但这段时间界面仍被遮罩。为更灵敏,可在遮罩层监听 mousemove 并调用 notifyActivity,让主进程立刻清除空闲。
// 在 ScreensaverComponent 中增加
@HostListener('mousemove') onMove() {
this.idle.notifyActivity();
}
此外,当 Electron 窗口处于开发者工具调试或全屏演示时,可以临时提高阈值或禁用屏保。通过主进程读取命令行参数或配置文件,实现灵活策略。
最后要注意,屏保组件内不要放置需要联网的敏感操作,因为空闲期间系统可能进入低功耗。所有退出逻辑应当本地完成,避免依赖网络往返导致界面卡死。
小结对比
| 方案 | 检测位置 | 可靠性 | 实现复杂度 |
|---|---|---|---|
| 纯前端定时器 | 渲染进程 | 低,易漏判失焦 | 低 |
| 主进程+IPC | 主进程 | 高,覆盖系统级状态 | 中 |
从表中可见,主进程配合 IPC 的方案虽多写一些进程间代码,但换来的是稳定的空闲识别。Angular 的响应式服务又能把这种稳定性无缝转成界面反馈,是桌面应用屏保的推荐做法。
AngularElectronidle_screensaver修改时间:2026-08-01 16:57:33