上一篇文章《从零开始用 electron 手撸一个截屏工具》发布之后发现阅读的朋友还不少,不过工具真正使用的时候就发现了问题,所以为了让我们的截图工具更好用,就又做了很多优化,当然了也遇到了很多坑。

截屏效果图:

项目修改后的完整代码依然是之前的地址: github.com/chrisbing/e… 欢迎大家关注

接下来就列举一下解决的问题和具体做法


1. 截图一瞬间卡顿问题

先放上一版截图代码

console.time('capture')
desktopCapturer.getSources({types: ['screen'],thumbnailSize: {width: width * scaleFactor,height: height * scaleFactor,}
}, (error, sources) => {console.timeEnd('capture')let imgSrc = sources[0].thumbnail.toDataURL()let capture = new CaptureRenderer($canvas, $bg, imgSrc, scaleFactor)
})
复制代码

desktopCapturer.getSources 会导致整个程序挂起,挂起时间与屏幕分辨率、屏幕数量和电脑性能有关。 在自用的 Macbook Pro 外接2K 显示器的情况下截图可以卡住2秒以上,而且鼠标还会出现等待的样式,这个体验是相当差了

所以就需要寻求替代方案了,参考 github.com/electron/el… 和 github.com/electron/el… 这两个 Issue,替代方案有两种,第一种用第三方原生的一些截屏程序,第二种是利用getUserMedia

我选了第二种方法,主要是觉得简单吧。第一种方法大家可以尝试一下,也欢迎反馈结果。

下面附上修改后的代码

    const handleStream = (stream) => {document.body.style.cursor = oldCursordocument.body.style.opacity = '1'// Create hidden video taglet video = document.createElement('video')video.style.cssText = 'position:absolute;top:-10000px;left:-10000px;'// Event connected to streamlet loaded = falsevideo.onloadedmetadata = () => {if (loaded) {return}loaded = true// Set video ORIGINAL height (screenshot)video.style.height = video.videoHeight + 'px' // videoHeightvideo.style.width = video.videoWidth + 'px' // videoWidth// Create canvaslet canvas = document.createElement('canvas')canvas.width = video.videoWidthcanvas.height = video.videoHeightlet ctx = canvas.getContext('2d')// Draw video on canvasctx.drawImage(video, 0, 0, canvas.width, canvas.height)if (this.callback) {// Save screenshot to png - base64this.callback(canvas.toDataURL('image/png'))} else {// console.log('Need callback!')}// Remove hidden video tagvideo.remove()try {stream.getTracks()[0].stop()} catch (e) {// nothing}}video.srcObject = streamdocument.body.appendChild(video)}     // mac 和 windows 获取 chromeMediaSourceId 的方式不同if (require('os').platform() === 'win32') {require('electron').desktopCapturer.getSources({types: ['screen'],thumbnailSize: { width: 1, height: 1 },}, (e, sources) => {let selectSource = sources.filter(source => source.display_id + '' === curScreen.id + '')[0]navigator.getUserMedia({audio: false,video: {mandatory: {chromeMediaSource: 'desktop',chromeMediaSourceId: selectSource.id + '',minWidth: 1280,minHeight: 720,maxWidth: 8000,maxHeight: 8000,},},}, handleStream, handleError)})} else {navigator.getUserMedia({audio: false,video: {mandatory: {chromeMediaSource: 'desktop',chromeMediaSourceId: `screen:${curScreen.id}`,minWidth: 1280,minHeight: 720,maxWidth: 8000,maxHeight: 8000,},},}, handleStream, handleError)}
复制代码

代码有点多,主要也是复制来的。他的原理是用 getUserMedia 来录屏,获取到视频资源,然后将视频绘制到 canvas 上,最后转换成 url。

修改后截屏不会出现整个程序挂起的情况,时间也缩小到600ms 左右,这个时间对于截图来说已经是可以接受的了。

2. 多屏幕支持

当电脑有多个显示器的情况,多屏截图就很重要了,之前只提到了一个屏幕的情况,那多屏应该怎么处理呢?

由于全屏情况,窗口只能占据一个屏幕,所以多屏截图只能用多个截屏窗口来处理了(windows 或许有办法让全屏窗口跨屏显示,待尝试)

首先创建窗口就需要先获取屏幕数量,循环创建

const captureScreen = (e, args) => {if (captureWins.length) {return}const { screen } = require('electron')let displays = screen.getAllDisplays()// 循环创建截屏窗口captureWins = displays.map((display) => {let captureWin = new BrowserWindow({// window 使用 fullscreen,  mac 设置为 undefined, 不可为 falsefullscreen: os.platform() === 'win32' || undefined,width: display.bounds.width,height: display.bounds.height,x: display.bounds.x,y: display.bounds.y,transparent: true,frame: false,movable: false,resizable: false,enableLargerThanScreen: true,hasShadow: false,})captureWin.setAlwaysOnTop(true, 'screen-saver')captureWin.setFullScreenable(false)captureWin.loadFile(path.join(__dirname, 'capture.html'))// 调试用// captureWin.openDevTools()// 一个窗口关闭则关闭所有窗口captureWin.on('closed', () => {let index = captureWins.indexOf(captureWin)if (index !== -1) {captureWins.splice(index, 1)}captureWins.forEach(win => win.close())})return captureWin})}
复制代码

然后每个窗口截取当前屏幕的画面进行操作,获取当前屏幕可以下面的方法

// 因为窗口是全屏的, 所以可以直接用 x, y 来对比
const getCurrentScreen = () => {let { x, y } = currentWindow.getBounds()return screen.getAllDisplays().filter(d => d.bounds.x === x && d.bounds.y === y)[0]
}
复制代码

然后根据问题1的截图代码就可以获取到当前屏幕的截图, 其中chromeMediaSourceId代表的就是屏幕的 ID

改到这里,大体上就差不多了,但是还有个小问题,因为是多个窗口,每个窗口都可以通过拖拽选区图片区域。参考 QQ 在 Mac 上的做法,当一个屏幕有选区了,另一个屏幕上禁止操作

多窗口互通的话,使用了 ipc 通讯。窗口选区后发给 main 进程,main 进程广播给其他窗口,其他窗口接收后禁止操作。

    // main 进程ipcMain.on('capture-screen', (e, { type = 'start', screenId, url } = {}) => {// ...if (type === 'select') {captureWins.forEach(win => win.webContents.send('capture-screen', { type: 'select', screenId }))}})
复制代码
    // renderer 进程ipcRenderer.on('capture-screen', (e, { type, screenId }) => {if (type === 'select') {if (screenId && screenId !== currentScreen.id) {capture.disable()}}})
复制代码

3. Mac 下截取全屏窗口

Mac 下让窗口显示在全屏窗口之上的话,需要一段神奇的代码,当然代码的写法是查搜出来的,但是具体原来还不是很清楚,貌似是一些 hack 的手段吧。

在我这我只能称之为"黑魔法"

下面一段代码放在创建截屏窗口的代码后面

let captureWin = new BrowserWindow({// window 使用 fullscreen,  mac 设置为 undefined, 不可为 falsefullscreen: os.platform() === 'win32' || undefined,width: display.bounds.width,height: display.bounds.height,x: display.bounds.x,y: display.bounds.y,transparent: true,frame: false,movable: false,resizable: false,enableLargerThanScreen: true,hasShadow: false,show: false,})// 黑魔法...app.dock.hide()captureWin.setAlwaysOnTop(true, 'screen-saver')captureWin.setVisibleOnAllWorkspaces(true)captureWin.setFullScreenable(false)captureWin.show()app.dock.show()captureWin.setVisibleOnAllWorkspaces(false)复制代码

经过上面的优化后,这个截图工具已经可以达到产品级了。当然还有一些不足的地方,比如跨屏截图,涂鸦,各种各样的体验细节吧,后面有时间优化完,再来和大家分享!!!

Electron 截图踩坑和优化集合相关推荐

  1. html2canvas图片的文字偏移,html2canvas在Vue项目踩坑-生成图片偏移不完整

    背景 最近做一个Vue项目需求是用户长按保存图片,页面的数据是根据不同id动态生成的,页面渲染完生成内容图片让用户长按保存的时候,把整个页面都保存起来. 在项目遇到的坑是图片能生成,可是生成的图片总是 ...

  2. 用户数从 0 到亿,我的 K8s 踩坑血泪史

    作者 | 平名 阿里服务端开发技术专家 导读:容器服务 Kubernetes 是目前炙手可热的云原生基础设施,作者过去一年上线了一个用户数极速增长的应用:该应用一个月内日活用户从零至四千万,用户数从零 ...

  3. unity webgl开发踩坑——从开发、发布到优化

    目录 前言 环境 unity webgl的一些注意点 videoplayer修改-->Video Player WebGL插件 text修改--解决不能显示汉字问题 制作.读取ab包 unity ...

  4. TVM: Deep Learning模型的优化编译器(强烈推荐, 附踩坑记录)

    本文作者是阿莱克西斯,原载于知乎,雷锋网(公众号:雷锋网)获得授权转载. (前排提醒,本文的人文内容部分稍稍带有艺术加工,请保持一定的幽默感进行阅读) 关注我最近想法的同学应该知道我最近都在把玩 TV ...

  5. node.js + Electron 调用 Windows API 踩坑日记

    前排提示:深坑,建议使用 C#.C++.VB 等方式 + 本地网络传输或进程管道通信替代. TOOLS 工具 Node.js(12.18.1) Electron(此处使用 ^2.0.0,因为 cef ...

  6. 使用RKNN部署CRNN模型踩坑优化历程

    序言 前段时间使用RKNN部署一个文字识别模型,因为文字识别模型用的是目前最普遍使用的CRNN模型,结构也相对简单:卷积+LSTM+全连接,都是比较元老级别的算子,本来已经部署的过程会很顺利,结果发现 ...

  7. 日常踩坑记录-汇总版

    开发踩坑记录,不定时更新 心得 RTFM 严谨的去思考问题,处理问题 严格要求自己的代码编写习惯与风格 注意 单词拼写 20200207 mybatis plus 自带insert插入异常 sql i ...

  8. 分布式深度学习最佳入门(踩坑)指南

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 作者丨Lyon@知乎(已授权) 来源丨https://zhuanla ...

  9. linux python wps doc 转 txt_耗时一周尝试踩坑,整理了一些Python实用知识点

    很零碎的知识点,有的是踩过的坑,不成系统,但是绝对很有用,知道的可以回顾下,不太了解的可以多学习下 1.Python连接MySQL加编码 记得加charset,没加的话部署Linux服务器运行可能有中 ...

最新文章

  1. oracle根据_分割字符串,oracle分割字符串函数
  2. Java Web Jsp
  3. springboot + mybatis + gradle项目构建过程
  4. .NET 6 Talk Party 2|.NET Core 与行业
  5. English trip -- VC(情景课)5 Around Town
  6. django-元选项
  7. Lua4.0 实现#操作,获取table大小
  8. 性能翻倍!斯坦福Matei团队推出机器学习模型优化新方法
  9. python从网页提取文本_从网页中提取文本
  10. vue 使用 vue-wechat-title 动态设置title
  11. MYSQL命令行闪退问题解决
  12. 【VUE】vue安装教程
  13. android竖屏固定,安卓教程:设置竖屏固定壁纸
  14. 小猿圈之初识python基础知识
  15. hexo图片展示-blog图床迁移至七牛云
  16. 腾讯面试题:买200返100优惠券,实际上折扣是多少?
  17. 计算机网络名词解释www万维网,网络名词解释
  18. ./configure 的用法
  19. java击鼓传花游戏list_集体互动游戏《击鼓传花游戏》
  20. AxMath的常用操作

热门文章

  1. 【python 爬虫】豆瓣评论全爬取含展开
  2. java finalize 何时被调用_Java禁止使用finalize方法
  3. easyexcel 检查表头是否匹配_Java EasyExcel读取Excel表头数据的方法及示例代码
  4. 我们应该怎么看待低代码呢?
  5. 罗永浩:一直走在人格营销的路上
  6. micropython开发板有什么用_MicroPython入门:能跑MicroPython开发板大盘点!
  7. RN:pushy热更新
  8. 安信可A7模块GPRS功能测试及初步学习AT指令
  9. 微信朋友圈将开通访客记录功能?网友已炸锅…
  10. 教你一招,如何将vr网站中的360全景图图片和全景漫游文件下载到本地电脑