screen 与 nativeImage:获取屏幕信息与处理图像
本教程共 45 篇 · 第 25 篇 · 更新于 2026-08-03
25. screen 与 nativeImage:屏幕信息与图像处理
本节目标
- 用
screen获取主显示器、所有显示器与工作区尺寸。 - 理解物理像素与 DIP 坐标的区别。
- 用
nativeImage从路径、缓冲、DataURL 创建图像。 - 对图像做缩放、裁剪并导出为 PNG/JPEG/DataURL。
- 把
nativeImage用于托盘与窗口图标等场景。
25-1 screen 模块的基础
screen 模块用来查询屏幕相关的信息:有哪些显示器、每个显示器多大、可用工作区(扣除任务栏后的区域)是多少。它在主进程里运行,并且必须在 app 的 ready 事件发出后才能使用。
和渲染进程里的 window.screen 不同,Electron 的 screen 是来自 electron/main 的独立模块。在渲染进程或 DevTools 里,window.screen 是浏览器保留的 DOM 属性,不能用 require 覆盖它。
使用 screen 最常见的目的,是让窗口刚好铺满某个显示器的可用区域,或者把新窗口放到外接显示器上。
// 主进程 main.js
const { app, BrowserWindow, screen } = require('electron/main')
app.whenReady().then(() => {
const primary = screen.getPrimaryDisplay()
const { width, height } = primary.workAreaSize
const win = new BrowserWindow({ width, height })
win.loadFile('index.html')
})
这里用的是 workAreaSize 而不是 size。workAreaSize 扣除了系统任务栏、Dock 等占用的空间,窗口不会被这些系统 UI 遮挡。
25-2 多显示器与坐标系
当你连接了外接显示器,screen.getAllDisplays() 会返回所有显示器的数组。每个显示器对象带 bounds(在全局坐标系里的位置和尺寸)等字段。
下面的例子找到「非主显示器」的外接屏,把窗口放到它的左上角偏移处。
// 主进程 main.js
const { app, BrowserWindow, screen } = require('electron/main')
app.whenReady().then(() => {
const displays = screen.getAllDisplays()
const external = displays.find((d) => d.bounds.x !== 0 || d.bounds.y !== 0)
if (external) {
const win = new BrowserWindow({
x: external.bounds.x + 50,
y: external.bounds.y + 50
})
win.loadFile('index.html')
}
})
这里要理解两种坐标:物理屏幕点(硬件原始像素)和设备无关点 DIP(按 DPI 缩放后的虚拟点)。Electron 返回的坐标默认是 DIP,getCursorScreenPoint() 返回的也是 DIP 点。Windows 和 Linux 上还有 screenToDipPoint / dipToScreenPoint 做两者换算,但 Wayland 下支持有限。
除了上述方法,还有 getDisplayNearestPoint(point) 与 getDisplayMatching(rect) 用来按坐标或区域找最近的显示器。
25-3 screen 的事件
screen 是一个 EventEmitter,会在显示器变化时发出事件。最常用的三个是:
display-added:有新显示器接入。display-removed:显示器被移除。display-metrics-changed:某个显示器的bounds、workArea、scaleFactor、rotation等发生变化。
这些事件适合用来做「多屏布局自适应」,比如窗口所在的屏幕被拔掉后,把窗口移回主屏。
// 主进程 main.js
const { screen } = require('electron/main')
screen.on('display-metrics-changed', (event, display, changedMetrics) => {
console.log('显示器指标变化:', changedMetrics)
})
25-4 nativeImage 的创建方式
nativeImage 提供了一套统一的图像操作接口,常用于托盘图标、窗口图标、通知图标等。它的创建方式有多种,按来源区分:
createFromPath(path):从 PNG/JPEG 文件创建,路径不存在时返回空图像。createFromBuffer(buffer[, options]):从内存缓冲创建,会先尝试按 PNG/JPEG 解码。createFromDataURL(dataURL):从 base64 的 DataURL 创建。createEmpty():创建一个空的透明图像。createFromBitmap(buffer, options):从原始位图数据创建,需指定宽高。
// 主进程 main.js
const { nativeImage } = require('electron/main')
const fromPath = nativeImage.createFromPath('/Users/me/icon.png')
const fromUrl = nativeImage.createFromDataURL('data:image/png;base64,iVBORw0KGgo...')
const empty = nativeImage.createEmpty()
console.log('是否为空:', empty.isEmpty())
Electron 接受图像参数的 API,既可以传文件路径字符串,也可以传 NativeImage 实例。两者等价,但用实例能进一步做缩放、裁剪等处理。
25-5 缩放、裁剪与导出
拿到 NativeImage 后,可以做常见变换。resize(options) 缩放图像,只给 width 或 height 之一时会保持原宽高比。crop(rect) 按矩形区域裁剪,返回新的 NativeImage。
// 主进程 main.js
const { nativeImage } = require('electron/main')
const image = nativeImage.createFromPath('/Users/me/photo.png')
// 缩放到宽 100,高度按比例自动
const resized = image.resize({ width: 100 })
// 裁出左上角 50x50 的区域
const cropped = image.crop({ x: 0, y: 0, width: 50, height: 50 })
// 取尺寸信息
console.log(resized.getSize())
导出方面,toPNG() 与 toJPEG(quality) 把图像编码为 Buffer,toDataURL() 导出为 DataURL 字符串,方便直接塞进 <img src>。
// 主进程 / preload.js
const { nativeImage } = require('electron/main')
const image = nativeImage.createFromPath('/Users/me/icon.png')
const pngBuffer = image.toPNG()
const dataUrl = image.toDataURL()
25-6 高密度屏与模板图像
在支持高 DPI 的屏幕上(如 macOS Retina),推荐为同一图标提供多套尺寸。约定是在文件名后加 @2x、@3x 后缀,例如 icon.png 与 icon@2x.png,Electron 会自动按屏幕密度选择。
Windows 上还支持 ICO 图标,建议包含 16/32/48/256 等多种尺寸以保证清晰度。macOS 的菜单栏图标可用「模板图像」:文件名以 Template 结尾(如 trayTemplate.png),或以代码 setTemplateImage(true) 标记,它只含黑白与透明度,能自动适配明暗菜单栏。
// 主进程 main.js
const { nativeImage } = require('electron/main')
const trayIcon = nativeImage.createFromPath('/Users/me/trayTemplate.png')
trayIcon.setTemplateImage(true)
25-7 在渲染进程安全地使用图像
nativeImage 文档标注主进程和渲染进程都可用,但若开启了上下文隔离,应在 preload 里调用并通过 contextBridge 暴露。下面演示把剪贴板图像导出为 DataURL 给页面显示。
// 预加载脚本 preload.js
const { contextBridge, ipcRenderer } = require('electron')
const { clipboard, nativeImage } = require('electron/main')
contextBridge.exposeInMainWorld('imageApi', {
clipboardToDataURL: () => {
const img = clipboard.readImage()
return img.isEmpty() ? null : img.toDataURL()
}
})
// 渲染进程 renderer.js
const url = window.imageApi.clipboardToDataURL()
if (url) document.getElementById('preview').src = url
25-8 多比例表示与动态构建
NativeImage 还提供 getScaleFactors(),返回当前图像包含的所有比例表示(如 [1, 2])。如果你要针对不同屏幕密度手动补一套图,可以用 addRepresentation(options) 往一个空图像上追加表示。
这在程序化生成图标时很有用:你不一定有现成的多尺寸文件,却可以根据一份高清缓冲生成 1x、2x 两套,交给系统按屏幕挑选。
// 主进程 main.js
const { nativeImage } = require('electron/main')
const hiRes = nativeImage.createFromPath('/Users/me/icon@2x.png')
const factors = hiRes.getScaleFactors()
console.log('包含的比例:', factors)
// 程序化增补表示
const img = nativeImage.createEmpty()
img.addRepresentation({
scaleFactor: 2,
buffer: hiRes.toBitmap()
})
理解了 screen 与 nativeImage 之后,你已经能为窗口、托盘、通知都配上合适的图标,也能按显示器工作区精确摆放窗口,这是做出「像样桌面应用」的基础能力。
补充一点:Windows 与 macOS 支持 createThumbnailFromPath(path, size),可直接为任意文件生成缩略图(macOS、Windows 可用),非常适合做文件预览列表。它返回 Promise,解析为 NativeImage,省去你手动解码。
DPI 后缀方面,除了 @2x,Electron 还识别 @1x、@1.5x、@3x、@4x 等一系列比例。把同一图标的不同尺寸放在同一目录,文件名不带后缀地传给 API,系统会按当前屏幕密度自动挑选最匹配的那张,既清晰又省内存。
实际项目里,最常见的做法是在应用启动时根据主显示器信息决定窗口初始大小。比如先用 screen.getPrimaryDisplay() 拿到 workAreaSize,再按七成或八成比例创建窗口,这样既不会过小难以使用,也不会超出可用区域被任务栏遮挡。配合 screen.on('display-metrics-changed'),还能在用户拖动窗口到另一块屏幕、或改变分辨率时,实时调整窗口尺寸或重新居中,体验更顺滑。
getScaleFactors 与 addRepresentation 的组合也值得一提:当你只有一张高清原图时,可以生成多种比例表示,确保无论用户屏幕是 1x 还是 3x,图标都锐利不发虚。
常见误区
不要把 getPrimaryDisplay().size 当作可用区域。它包含任务栏等系统 UI 占用的空间,窗口铺满后会被遮挡,应当用 workAreaSize。
不要忽略 createFromPath 可能返回空图像。路径错误时不会抛异常,而是返回空图像,使用前最好用 isEmpty() 判断。
v43 起 toBitmap() 默认把像素数据归一化到 sRGB 色彩空间。如果你需要保留原始色彩空间,要在 options 里显式传入图像原本的 colorSpace,否则颜色可能和预期不一致。