Mar 06, 2020
Electron 截屏方案分析
实现一个在 Electron 内截屏的功能,大概有六种实现方式,讨论一下选择哪个方案。最后再说一下高分屏的截图如何变得清晰。
可选方案
第一种,使用 Canvas 截图
这种技术有一个非常有名的库:html2canvas,可以支持相对比较简单的 DOM 截图
目前一直是处于实验状态(very experimental state),如果理解这个库的限制,那么是一个还不错的选择
原理是使用 BoundCurves
对 DOM 进行 path 绘制。例如下面一段选自 html2canvas 库中的代码:
1 | this.topLeftPaddingBox = |
计算每一个 DOM 的形状,再把这些数据,比如 topLeftPaddingBox 放到 Stack 中,最后放在队列中,一个一个的绘制。
优势
- 当然是兼容性非常好, 支持 canvas 的就行
劣势
- 因为是要把现存的 DOM 搬到 canvas 中,所以只能识别库支持的 css 属性,比如 transform 不支持。
- 获取资源要同源,如果有其它跨域资源,布局会混乱
- 大量计算工作,性能损耗较大
第二种,使用 SVG 构图
如下简单的例子,可以把 DOM 通过 svg 的 foreignObject 放到 canvas 中,就可以截图了。
1 | // 这段代码出自它处 |
做得好的是 rasterizeHTML.js,同时可以支持直接填写url,然后获取后自己截图。
rasterizeHTML.js 和 html2canvas 的区别
之前是 rasterizeHTML.js 使用 foreignObject,而 html2canvas 自己从最基础的开始。
推荐直接使用 foreignObject 更加安全,目前 html2canvas 和 rasterizeHTML 都兼容使用 foreignObject (html2canvas 看源代码里面已经支持了)。
第三种,使用 Chrome 的 getDisplayMedia
1 | const captureStream = await navigator.mediaDevices.getDisplayMedia({ |
比如在 Chrome 中执行,会弹出下面的窗口,然后开启屏幕分享:
![](_image/Screen Shot 2020-03-07 at 00.10.21.png)
这种可以实现录制整个屏幕,或者某个 Tab,从而分享画面,不过录音这个功能会碰到一些阻碍。
优势
- 实现非网页内的内容也可以截取
劣势
- 需要用户手动允许系统的屏幕录像权限
- 获得的视频需要截取
第四种,使用 Electron 的 webContents.capturePage
Electron 内置了一个截取网页内的接口 webContents.capturePage ,如下:
1 | const remote = require('electron').remote |
优势
- 简单,快捷
- 支持截取完整的页面
劣势
- 只能截取当前页面的内容
第五种,使用 Electron DesktopCapturer module
五的第一种:
DesktopCapturer 模块里面可以通过设置录屏的 video,然后通过 canvas 获取录制屏幕数据。前面的 getDisplayMedia
也可以通过这个方式获取到截屏图片。
例如下面的代码:
1 | const { desktopCapturer } = require('electron') |
五的第二种:
之前看到有个地方,用 Thumbnail 拿来做屏幕截图 :P
1 | const { desktopCapturer } = require('electron') |
优势
- 可以搜索特定窗口进行录屏
- electron 封装了一层,使用更加简便
劣势
- 如同前面的 getDisplayMedia,也需要用户手动开启屏幕录像权限
- 如果用 desktopCapturer 的 thumbnail 来截屏,耗性能
第六种,使用系统原生的屏幕录制接口
这种就不细讲了,每端都适配复杂的原生接口,这样就已经迷失方向了。
截屏的图片不是 2x 图片
如果用上面的任何一种方法,在高分屏里面截图(window.devicePixelRatio === 2),最后得到的图片还是图片宽高放大一倍,但像素还是低一倍。
后来发现其实是图片头中有记录 meta 信息。里面就有关于 dpi 的信息。找到包 changedpi 可以修改 canvas 和 DataUrl 拿到的图片 dpi。
1 | // 创建一个标准的 72 dpi |
canvas 一般情况下,最高只能获得 72 dpi 分辨率的图片。
总结
每个点都没有太详细的讲,但已经可以通过这些选择最终的方案了。最后当然推荐使用的是 webContents.capturePage 的方法。简单易用,在功能上能做到最好。
参考
- https://stackoverflow.com/questions/16870404/whats-the-difference-between-html2canvas-and-rasterizehtml-js
- https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia
- https://ourcodeworld.com/articles/read/280/creating-screenshots-of-your-app-or-the-screen-in-electron-framework)