YIXUNFE / blog

文章区
151 stars 25 forks source link

利用 Electron 开发快速截图工具(二) #65

Open YIXUNFE opened 8 years ago

YIXUNFE commented 8 years ago

利用 Electron 开发快速截图工具(二)

上一篇我们讲到主界面开发完成,这一篇我们讲子界面的开发。

子界面的生命周期比较短,罗列如下:

我们来看看子界面中的一个个功能是如何实现的。


子界面中元素

canvas 元素

我选用了 canvas 作为截图的容器,主要是考虑到后续用户划取选区时比较方便。

按钮元素

子界面中有三个按钮:

qq 20160327175631


子界面的打开与关闭

之前我们提到直接在主界面调用 window.close() 关闭窗口,但这会在子界面中引起报错:

Uncaught RangeError: Maximum call stack size exceeded

Electron 教程中提到了一点,在网页中调用 GUI API 很危险,应该让主进程执行 GUI 操作。

所以渲染进程(即网页)的关闭应该通过主进程执行。

Electron 提供了我们 ipc 模块实现主进程与渲染进程的通信,需要注意的是,不同的进程中需要引入不同的 ipc 模块。

//in main process 
var ipc = require('electron').ipcMain;

//in renderer process
var ipc = require('electron').ipcRenderer;


主界面点击截图按钮时会通知主进程打开子界面:

//in renderer process
document.getElementById('capture').addEventListener('click', function () {
  ipc.send('create-sub-window')
})

//in main process 
ipc.on('create-sub-window', function (e, wh) {
  subWindow = new BrowserWindow({...})
  subWindow.loadURL('file://' + __dirname + '/sub.html')
})

当用户点击子界面的关闭按钮时,子界面通过 ipc 模块发送一个“关闭”消息至主进程,通知关闭子界面。

//in renderer process
ipc.send('close-subwindow')

//in main process 
ipc.on('close-subwindow', function () {
  subWindow.close()
})


用户截图

用户截图部分主要是通过用户在 canvas 上划取选区,最后得到一个参数对象:

{
  x: x, 
  y: y, 
  width: width,
  height: height
}
由于截图部分和 Electron 并不是很搭噶,大家可以从 项目 中查看相关代码。

这个参数可以用于 Electron 进行网页截图。

// electron browserWindow capture page
subWindow.capturePage({
  x: x, 
  y: y, 
  width: width,
  height: height
}, function (image) {
 ...
})

capturePage 方法接受两个参数,第一个参数是截图的数据参数,包含了截图的起始点与宽高。值得注意的是,高宽必须是正数(canvas 的 rect 方法接受负数的高宽值)。第二个参数就是截图后的回调方法,会参入截得的 nativeImage 对象。如果省略第一个参数,会获得整个网页的截图哦。


网页截图后,需要保存至剪贴板。这时候需要调用 Electron 的剪贴板模块:

var clipboard = require('electron').clipboard

clipboard.writeImage(image) //nativeImage object

向剪贴板中写入图片需要使用 writeImage 方法,并且需要传入从 subWindow.capturePage 方法中得到的 nativeImage 对象。完成后我们可以从 Ctrl + V 的快捷操作中验证。


保存文件

保存文件功能本身是通过 Node.js 的文件系统完成的,但是可以结合 Electron 的对话框,可以使用户自己决定保存目录。

Electron 的 dialog 模块只有 4 个方法可以使用,这里我们使用 showSaveDialog 方法让用户指定目录和文件名。

var dialog = electron.dialog,
  fs = require('fs')

dialog.showSaveDialog({title: '请选择保存路径', defaultPath: 'E:/', filters: [
  { name: 'Images', extensions: ['png'] }
]}, function (p) {
  fs.writeFile(p, image.toPng(), function () {
    ...
  })
})

由于需要用到截图得到的 nativeImage 对象,需要在主进程中组织代码。Node.js 中的 writeFile 方法支持写入 buffer 数据,所以第二个参数我们使用了 image.toPng 方法,它会返回图片对应格式的 buffer 数据,如果想要 jpg 格式的话可以使用 image.toJpeg 方法。

image.toJpeg(quality)


打包应用

打包在开胃篇中提到过,使用 electron-packager 可以简单方便的实现打包。

npm install electron-packager --save-dev

为了使用方便,我们在 package.json 中加上一个打包命令:

"scripts": {
  "start": "electron main.js",
  "package": "electron-packager ./ screenCapturer --all --out ~/Desktop/screenCapturer --version 0.37.2 --overwrite --icon=./app/app-icon.ico"
}

以后每次打包,直接跑一下命令即可。

npm run-script package
第一次打包时需要下载相关的资源包,之后就不需要下载了。


项目地址

传送门


Thanks


littledu commented 8 years ago

请教,当我们改了主进程的文件后,有没办法让它不用重启就可以直接刷新生效的?官方的 debug 看得迷迷糊糊

YIXUNFE commented 8 years ago

@littledu 开启 devtool 的时候,焦点在 devtool 上时可以直接 F5 刷新。

自动刷新没有尝试,但是拍脑袋想想可能可以通过 IDE 来配置,比如 WebStrom 可以配置自动刷新 NodeJS 程序。

littledu commented 8 years ago

不是自动刷新哈,是可以不用重新执行 electron --debug .来重新启动应用,要在 devtool 那里刷新就能刷新主进程?还没试过,晚上试试

YIXUNFE commented 8 years ago

@littledu

可以尝试这个包:https://github.com/Quramy/electron-connect

  // Restart browser process
  gulp.watch('app.js', electron.restart);

  // Reload renderer process
  gulp.watch(['index.js', 'index.html'], electron.reload);