基于 Electron 开发即时通讯 APP,截图功能后期势必成为一个刚需的功能。Webkit 中的 desktopCapture API 只能截取当前窗口的画面,用户的实际需求还是需要全屏选取范围截取。如果需要一个跨平台的插件实现,可能需要使用原生开发出一个截图应用,然后再暴露给 Electron 调用。

如果你只是需要实现 macOS 平台的截图功能,那么内置的 screencapture 程序也许已经满足了我们的使用需求。我们先在 terminal 中查看下 screencapture 程序有哪些 API 提供

1
2
3
4
5
6
7
8
screencapture -h
screencapture: illegal option -- h
usage: screencapture [-icMPmwsWxSCUtoa] [files]
-c force screen capture to go to the clipboard
-b capture Touch Bar - non-interactive modes only
-C capture the cursor as well as the screen. only in non-interactive modes
...
...

screencapture 程序提供了命令行式的调用方式,我们只需要在 Electron 的主进程中使用 NodeJS 运行我们需要执行的命令就可以了。
二次封装有利于我们后期的维护和替换,很高兴轮子已经有人帮我们造好了:rogerbf/macos-screencapture

我们的需求希望用户截图后,截取的图片需要保存在系统的剪切板和指定的目录中。但是发现使用 -c 后,不能同时保存至指定的目录中,那只能曲线救国了,先保存在指定的目录中,然后使用 clipboard API 写入到剪切板中。

假设我们通过某个入口执行起截图功能:
主进程:main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function openScreenCapturer() {
//指定保存的目录是在 /用户目录/图片 中
const basePath = path.join(require('home-path')(), './Pictures/cn.lunkr.screenCapture/')

//使用当前时间作为图片名称
const imageName = `lunkr-${utils.parseTime(new Date(), '{y}{m}{d}{h}{i}{s}')}.png`
const imagePath = basePath + imageName
const exists = pathExists.sync(basePath)

//需要预先判断这个目录是否存在,不存在的话执行一次 mkdir
if(exists) {
doScreencapture(imagePath)
} else {
try {
if(mkdir.sync(basePath)) doScreencapture(imagePath)
} catch (e) {
console.log(e)
}
}
}

调起系统的截图程序,然后通知渲染进程进行二次确认
主程序:main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function doScreencapture(path) {
const pArr = path.split('/')

//调用 screencapture,-s 强制鼠标选择模式
screencapture({
path: path,
options: ['-s']
}).then(path => {
//截图成功的回调,使用 nativeImage API 从 PATH 中读取出保存的图片
const image = nativeImage.createFromPath(path)

//写入到剪切板
clipboard.writeImage(image)

//将图片读取为 dataURL 格式
const dataURL = image.toDataURL()

//使用内置的事件系统,将 dataURL 格式的图片通知给渲染程序
mainWindow.webContents.send('screen-captured', {
dataURL,
fileName: pArr[pArr.length - 1]
})
}).catch(err => console.log(`error: ${err}`))
}

渲染程序监听到主程序的事件后,进行交互层面的用户二次确定
渲染程序:index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const ipcRenderer = window.Electron.ipc

//使用 ipc 监听主程序发送的事件
ipcRenderer.on('screen-captured', (event, message) => {
//将 dataUrl 的图片转换成 file 对象
const file = dataURLtoFile(message.dataURL, message.fileName)

//弹出确认预览框
openScreenCaptureModal({
file,
name: message.fileName,
dataURL: message.dataURL
})
})

//额外赠送~
export function dataURLtoFile(dataurl, filename) {
const arr = dataurl.split(',')
const mime = arr[0].match(/:(.*?);/)[1]
const bstr = atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while(n--){
u8arr[n] = bstr.charCodeAt(n)
}
return new File([u8arr], filename, {type:mime})
}

大概的实现思路和核心的代码就这些啦,希望对一些朋友有所帮助
效果如下图所示:

preview