如何用Node.js实现NotificationAPI2Image通知转图片?

来源:编程学习作者:孙志远头衔:网络博主
导读:本期聚焦于孙志远创作的《如何用Node.js实现NotificationAPI2Image通知转图片?》,敬请观看详情。服务端能不能像浏览器那样触发系统通知,并把通知内容渲染成一张图片?答案是可以的。Node.js 环境虽然没有 window.Notification 对象,但借助 node-notifier 和 Puppeteer 可以完成 NotificationAPI2Image 的核心链路。本文从通知数据结构设计入手,说明如何用 HTML 模板渲染通知卡片,再通过无头浏览器截图得到图片文件,最后把图片路径传给系统通知。文章会对比 Puppeteer 截图与 node-canvas 直接绘制两条路线的性能差异,给出可运行的完整代码示例,并讨论浏览器实例复用、图片缓存、字体渲染等生产环境问题。读者照着实现后,可以搭建一个将通知异步转成图片的 Node.js 服务,用于 IM 消息提醒、监控告警、日报摘要等场景。即使服务器没有图形界面,也只需要添加简单的启动参数即可完成部署。

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

如何用Node.js实现NotificationAPI2Image通知转图片?

服务端 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

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0917/58481.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。