在桌面应用中,打印功能往往是一个“看起来简单、做起来容易踩坑”的模块。Electron 底层使用 Chromium 的打印引擎,因此对现代 CSS 打印样式(@media print、@page 等)的支持非常好。但要把打印体验做到符合用户预期,你还需要理解 Electron 提供的几种打印模式,并知道如何在实际业务中组合它们。
这一节会从最基础的打印机调用讲起,逐步过渡到打印预览和静默打印,每个部分都会给出可运行的代码示例。
14.4.1 获取打印机列表与选择打印机
在 Electron 中,所有打印机相关的操作都集中在 webContents 上,因为打印总是针对某个具体的窗口内容。要获取当前系统已安装的打印机,可以使用 webContents.getPrintersAsync()(Electron 17+)或 webContents.getPrinters()。
获取打印机列表
// 主进程
const { BrowserWindow, ipcMain } = require('electron');
ipcMain.handle('get-printers', async () => {
const win = BrowserWindow.getFocusedWindow();
if (!win) return [];
const printers = await win.webContents.getPrintersAsync();
// 返回打印机列表,每个打印机包含 name、displayName、isDefault 等信息
return printers.map(p => ({
name: p.name,
displayName: p.displayName,
isDefault: p.isDefault,
status: p.status,
}));
});
在渲染进程中通常不会直接调用 webContents,而是通过 IPC 请求主进程获取打印机信息,填充到一个下拉选择框中。
// 渲染进程(preload 已暴露 ipcRenderer.invoke)
async function loadPrinters() {
const printers = await window.api.invoke('get-printers');
const select = document.getElementById('printer-select');
select.innerHTML = '';
printers.forEach(p => {
const option = document.createElement('option');
option.value = p.name;
option.text = `${p.displayName}${p.isDefault ? '(默认)' : ''}`;
select.appendChild(option);
});
}
14.4.2 打印预览:让用户确认设置
默认情况下,调用 webContents.print() 会直接弹出系统原生的打印对话框,这个对话框本身就包含预览功能。但有些产品希望在应用内提供一个更可控的“打印预览”区域,让用户在真正发送到打印机之前检查排版效果。
实现这一目的通常有两种思路:
- 利用
webContents.printToPDF生成临时 PDF,然后渲染预览
将 PDF 数据转为 Blob URL,在 <iframe> 或专门的 PDF 预览组件中展示。这种方式的优势是可以完全控制预览界面的样式,缺点是 PDF 的渲染效果与最终打印机输出可能有一些细微差异(主要是因打印机设置不同)。
- 通过 CSS 模式切换模拟打印效果
在页面上动态添加一个 @media print 等效的样式类,让用户看到分页后的页面外观,但这并不能完全还原真实打印机的页面设置(如页边距、页眉页脚)。
在实际产品中,兼顾体验和准确度的常见做法是第一种——生成 PDF 预览。下面是一个完整示例:
主进程生成 PDF 并返回缓冲区
ipcMain.handle('generate-print-preview', async (event, options) => {
const win = BrowserWindow.getFocusedWindow();
if (!win) throw new Error('没有活动窗口');
// options 可以包含 pageSize、margins、printBackground 等
const pdfBuffer = await win.webContents.printToPDF({
printBackground: true,
marginsType: 1, // 默认边距
pageSize: 'A4',
landscape: false,
...options,
});
return pdfBuffer; // 返回 Buffer
});
渲染进程显示预览
async function showPreview() {
const pdfBuffer = await window.api.invoke('generate-print-preview', {
pageSize: 'A4',
marginsType: 1,
printBackground: true,
});
const blob = new Blob([pdfBuffer], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const previewIframe = document.getElementById('preview-iframe');
previewIframe.src = url; // 在 iframe 中显示 PDF
}
注意:printToPDF 会尊重当前页面的 CSS @page 规则,因此你可以在样式表中定义纸张大小、页边距等,让预览更接近最终打印结果。
14.4.3 静默打印:直接发送到打印机
很多业务场景(如餐饮小票打印、物流面单打印)不需要用户每次都看到打印对话框,而是直接使用预设配置将内容输出到打印机。此时可以使用 webContents.print() 并传入 silent: true 选项,或者使用 webContents.printToPDF 得到文件后再通过系统打印命令输出。
方式一:使用 webContents.print() 静默打印
// 主进程
ipcMain.handle('silent-print', async (event, options) => {
const win = BrowserWindow.getFocusedWindow();
if (!win) throw new Error('没有活动窗口');
// options 指定打印机名、份数、页面设置等
await win.webContents.print({
silent: true,
printBackground: true,
deviceName: options.printerName, // 之前获取的打印机 name
copies: options.copies || 1,
margins: { top: 0, bottom: 0, left: 0, right: 0 },
color: true,
landscape: false,
});
});
调用这个方法时,不会弹出任何对话框,内容会直接发送到指定的打印机。如果 deviceName 不存在,则使用系统默认打印机。
方式二:生成 PDF 后用命令行打印(适用于热敏打印机等特殊场景)
有些热敏打印机不支持通过 Chromium 直接打印,或者需要特殊的纸张尺寸(如 80mm × 无限长)。此时可以先将内容渲染为一个特定尺寸的 HTML 页面,再通过 printToPDF 生成 PDF,最后调用系统命令(如 Windows 的 print 命令或 Linux 的 lp)来发送给打印机。
const { exec } = require('child_process');
async function silentPdfPrint(pdfPath, printerName) {
const cmd = `lp -d "${printerName}" "${pdfPath}"`; // Linux/macOS
// Windows 可以使用 SumatraPDF 或 PDFtoPrinter 等工具
exec(cmd, (error, stdout, stderr) => {
if (error) console.error('打印失败', error);
});
}
这种方式虽然步骤稍多,但灵活性极高,完全绕过了 Chromium 的打印限制,适合对打印精度和格式有严格要求的场景。
14.4.4 常见问题与注意点
- 打印内容必须是可见的窗口
print 和 printToPDF 只会打印当前 webContents 中实际渲染的内容,如果某个元素设置了 display: none,它不会出现在打印结果中。你可以专门创建一个隐藏的打印窗口,在里面加载需要打印的 HTML,然后对该窗口进行打印操作。
- CSS 打印样式的调试
在开发阶段,你可以打开 Chrome DevTools 的 Rendering 面板,勾选“Emulate CSS media type > print”,这样就能在不实际打印的情况下看到打印效果。
- 静默打印对打印机名称的要求
deviceName 必须与 getPrintersAsync() 返回的 name 字段完全一致,否则 Electron 会忽略该参数并使用默认打印机。某些打印机(特别是网络打印机)的名称可能包含特殊字符,注意处理。
- 跨平台打印差异
- Windows 下静默打印通常比较稳定。
- macOS 下静默打印可能需要额外的权限,如果应用沙盒化,还需要配置
com.apple.security.print权限。 - Linux 下依赖 CUPS 系统,确保打印机驱动已正确安装。
- 打印大批量任务
如果需要连续打印大量页面,建议使用队列机制,避免同时生成多个 PDF 导致内存飙升。
综合来看,Electron 提供的打印 API 已经能够覆盖绝大多数的业务需求。从简单的调起系统打印对话框,到完全自控的静默打印方案,你都可以在同一个技术栈中实现,无需依赖任何外部插件。