Electron 截图踩坑和优化集合
上一篇文章《从零开始用 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 截图踩坑和优化集合相关推荐
- html2canvas图片的文字偏移,html2canvas在Vue项目踩坑-生成图片偏移不完整
背景 最近做一个Vue项目需求是用户长按保存图片,页面的数据是根据不同id动态生成的,页面渲染完生成内容图片让用户长按保存的时候,把整个页面都保存起来. 在项目遇到的坑是图片能生成,可是生成的图片总是 ...
- 用户数从 0 到亿,我的 K8s 踩坑血泪史
作者 | 平名 阿里服务端开发技术专家 导读:容器服务 Kubernetes 是目前炙手可热的云原生基础设施,作者过去一年上线了一个用户数极速增长的应用:该应用一个月内日活用户从零至四千万,用户数从零 ...
- unity webgl开发踩坑——从开发、发布到优化
目录 前言 环境 unity webgl的一些注意点 videoplayer修改-->Video Player WebGL插件 text修改--解决不能显示汉字问题 制作.读取ab包 unity ...
- TVM: Deep Learning模型的优化编译器(强烈推荐, 附踩坑记录)
本文作者是阿莱克西斯,原载于知乎,雷锋网(公众号:雷锋网)获得授权转载. (前排提醒,本文的人文内容部分稍稍带有艺术加工,请保持一定的幽默感进行阅读) 关注我最近想法的同学应该知道我最近都在把玩 TV ...
- node.js + Electron 调用 Windows API 踩坑日记
前排提示:深坑,建议使用 C#.C++.VB 等方式 + 本地网络传输或进程管道通信替代. TOOLS 工具 Node.js(12.18.1) Electron(此处使用 ^2.0.0,因为 cef ...
- 使用RKNN部署CRNN模型踩坑优化历程
序言 前段时间使用RKNN部署一个文字识别模型,因为文字识别模型用的是目前最普遍使用的CRNN模型,结构也相对简单:卷积+LSTM+全连接,都是比较元老级别的算子,本来已经部署的过程会很顺利,结果发现 ...
- 日常踩坑记录-汇总版
开发踩坑记录,不定时更新 心得 RTFM 严谨的去思考问题,处理问题 严格要求自己的代码编写习惯与风格 注意 单词拼写 20200207 mybatis plus 自带insert插入异常 sql i ...
- 分布式深度学习最佳入门(踩坑)指南
点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 作者丨Lyon@知乎(已授权) 来源丨https://zhuanla ...
- linux python wps doc 转 txt_耗时一周尝试踩坑,整理了一些Python实用知识点
很零碎的知识点,有的是踩过的坑,不成系统,但是绝对很有用,知道的可以回顾下,不太了解的可以多学习下 1.Python连接MySQL加编码 记得加charset,没加的话部署Linux服务器运行可能有中 ...
最新文章
- oracle根据_分割字符串,oracle分割字符串函数
- Java Web Jsp
- springboot + mybatis + gradle项目构建过程
- .NET 6 Talk Party 2|.NET Core 与行业
- English trip -- VC(情景课)5 Around Town
- django-元选项
- Lua4.0 实现#操作,获取table大小
- 性能翻倍!斯坦福Matei团队推出机器学习模型优化新方法
- python从网页提取文本_从网页中提取文本
- vue 使用 vue-wechat-title 动态设置title
- MYSQL命令行闪退问题解决
- 【VUE】vue安装教程
- android竖屏固定,安卓教程:设置竖屏固定壁纸
- 小猿圈之初识python基础知识
- hexo图片展示-blog图床迁移至七牛云
- 腾讯面试题:买200返100优惠券,实际上折扣是多少?
- 计算机网络名词解释www万维网,网络名词解释
- ./configure 的用法
- java击鼓传花游戏list_集体互动游戏《击鼓传花游戏》
- AxMath的常用操作
热门文章
- 【python 爬虫】豆瓣评论全爬取含展开
- java finalize 何时被调用_Java禁止使用finalize方法
- easyexcel 检查表头是否匹配_Java EasyExcel读取Excel表头数据的方法及示例代码
- 我们应该怎么看待低代码呢?
- 罗永浩:一直走在人格营销的路上
- micropython开发板有什么用_MicroPython入门:能跑MicroPython开发板大盘点!
- RN:pushy热更新
- 安信可A7模块GPRS功能测试及初步学习AT指令
- 微信朋友圈将开通访客记录功能?网友已炸锅…
- 教你一招,如何将vr网站中的360全景图图片和全景漫游文件下载到本地电脑