Node.js 实现 NotificationAPI2Image 需要解决两个核心问题:第一,在服务端环境中如何触发系统通知;第二,通知内容如何被渲染成图片。本文会沿着通知数据定义、HTML 模板渲染、无头浏览器截图、系统通知发送这条链路展开,给出完整实现。

服务端 Notification 的替代方案
浏览器里的 Notification API 依赖 window 对象和用户授权,页面一旦关闭,通知也就无法继续由前端脚本触发。Node.js 没有 DOM 环境,因此需要寻找服务端通知库。node-notifier 是目前使用最广的跨平台方案,它在 macOS 上调用 osascript,在 Linux 上调用 notify-send,在 Windows 上通过 SnoreToast 或 PowerShell 来弹通知。这个库支持传入标题、正文、图标以及横幅图片路径,刚好可以作为 NotificationAPI2Image 的输出端。
但是 node-notifier 只接收已经存在的图片文件路径,它不会把一段富文本转换成图片。所以整个实现的重点并不是通知本身,而是通知图片的生成。开发者需要先决定通知卡片里有哪些字段,比如标题、正文、时间、来源应用名,然后把这些字段渲染成图片。生成步骤可以放在通知发送之前同步执行,也可以丢到队列里异步处理,视业务对延迟的要求而定。
如果需要把通知推送到移动端或 Web 端,图片生成完成后还可以与 web-push、邮件服务或企业微信机器人组合,形成统一的告警出口。本文以系统原生通知为例,展示如何组合 node-notifier 和 Puppeteer。
生成通知图片的两种技术路线
第一种路线是使用 Puppeteer 或 Playwright 启动无头浏览器,加载一段本地 HTML 模板,设置合适的视口尺寸后直接截图。这种方式的优势在于渲染结果与浏览器完全一致,CSS 布局、圆角、阴影、渐变甚至 Web 字体都能准确呈现,特别适合需要频繁调整样式的通知卡片。缺点是无头浏览器启动和渲染的成本较高,初次加载可能需要几百毫秒到几秒,内存占用也不小。
第二种路线是使用 node-canvas 在内存中绘制图片。node-canvas 基于 Cairo 图形库,体积远小于 Chromium,生成速度更快,适合吞吐量较高的简单卡片。但它对 CSS 的支持非常有限,文字换行、对齐、边距都需要手动计算坐标,修改样式不如 HTML 模板直观。如果通知只包含标题和正文两行文字,node-canvas 足够应付;如果希望卡片有头像、按钮、渐变背景,则维护成本会明显上升。
从工程可维护性出发,下面示例选择 Puppeteer。因为通知卡片样式通常来自设计稿,用 HTML 和 CSS 描述比手写 canvas 坐标更贴近实际协作方式。若未来卡片复杂度下降,也可以将截图函数替换为 node-canvas 实现,保持接口不变。
完整代码实现
先初始化项目并安装依赖。这里需要 node-notifier 和 puppeteer,其中 puppeteer 会下载 Chromium,安装耗时取决于网络环境。
npm init -y npm install node-notifier puppeteer
接下来定义通知数据结构。它至少包含标题、正文、图标和来源四个字段,后续渲染和发送都围绕这个结构展开。
const notificationData = {
title: '部署完成',
body: '订单服务已成功发布到生产环境',
source: 'CI/CD',
icon: 'https://ipipp.com/icon.png'
};
然后编写 HTML 模板函数。模板中设置了一张 800x400 的卡片,使用内联样式控制字体和间距。注意代码块中的尖括号已经做了转义,实际运行时把它们当作普通 HTML 字符串使用即可。
function buildHtml(data) {
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body { margin: 0; font-family: sans-serif; }
.card {
width: 780px;
height: 380px;
margin: 10px;
background: linear-gradient(135deg, #1f2937, #111827);
border-radius: 16px;
color: #fff;
padding: 30px;
box-sizing: border-box;
}
.title { font-size: 32px; font-weight: bold; }
.body { margin-top: 16px; font-size: 22px; line-height: 1.5; }
.source { margin-top: 30px; font-size: 18px; opacity: 0.7; }
</style>
</head>
<body>
<div class="card">
<div class="title">${data.title}</div>
<div class="body">${data.body}</div>
<div class="source">来源:${data.source}</div>
</div>
</body>
</html>`;
}
截图函数负责把 HTML 字符串写入临时文件,启动浏览器打开,再设置视口大小并输出图片。为了避免临时文件堆积,截图完成后可以删除 HTML 文件。
const fs = require('fs/promises');
const puppeteer = require('puppeteer');
let browserPromise;
async function getBrowser() {
if (!browserPromise) {
browserPromise = puppeteer.launch({
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
}
return browserPromise;
}
async function htmlToImage(html, outputPath = '/tmp/notification.png') {
const browser = await getBrowser();
const page = await browser.newPage();
await page.setViewport({ width: 800, height: 400, deviceScaleFactor: 2 });
const tmpFile = '/tmp/notification-card.html';
await fs.writeFile(tmpFile, html);
await page.goto(`file://${tmpFile}`);
await page.screenshot({ path: outputPath, type: 'png' });
await page.close();
await fs.unlink(tmpFile);
return outputPath;
}
最后调用 node-notifier 发送系统通知。Windows 平台上 contentImage 用于显示横幅大图,icon 是左下角小图标;macOS 上 contentImage 同样可以作为通知附加图片。
const notifier = require('node-notifier');
const path = require('path');
async function sendNotification(data) {
const imagePath = path.resolve('/tmp/notification.png');
const html = buildHtml(data);
await htmlToImage(html, imagePath);
notifier.notify({
title: data.title,
message: data.body,
icon: data.icon,
contentImage: imagePath,
sound: true,
wait: false
});
}
sendNotification(notificationData).catch(console.error);
生产环境优化与易错点
浏览器实例管理是第一个需要关注的地方。每次截图都调用 puppeteer.launch 会重复启动 Chromium,几秒钟的延迟会让通知失去实时性。正确做法是启动一次并复用,就像上面 getBrowser 函数那样,同时用并发控制限制同时打开页面的数量,避免内存暴涨。长时间运行后如果发现截图变慢,可以定时重启浏览器进程释放内存。
图片缓存同样重要。如果同一个通知内容被反复发送,可以先用标题、正文、样式的 hash 作为文件名,存在本地目录中,命中缓存就直接复用。这样能把截图耗时从几百毫秒降到几毫秒。缓存目录要定期清理,否则高频率通知会很快占满磁盘。
部署到 Linux 服务器时,无图形界面通常需要添加 --no-sandbox 参数,否则 Chromium 会因为权限错误无法启动。另一个常见问题是 emoji 在 CentOS 或 Ubuntu 最小化安装中显示为方框,需要安装 Noto Color Emoji 字体。Windows 下路径必须使用反斜杠,例如 C:\tmp\notification.png;Node.js 的 path.resolve 会根据平台自动处理,所以不要手动拼字符串。
Node.jsNotification API图片通知修改时间:2026-09-17 16:37:03