博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Electron 截图踩坑和优化集合
阅读量:6084 次
发布时间:2019-06-20

本文共 7050 字,大约阅读时间需要 23 分钟。

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

截屏效果图:

项目修改后的完整代码依然是之前的地址: 欢迎大家关注

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


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秒以上,而且鼠标还会出现等待的样式,这个体验是相当差了

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

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

下面附上修改后的代码

const handleStream = (stream) => {        document.body.style.cursor = oldCursor        document.body.style.opacity = '1'        // Create hidden video tag        let video = document.createElement('video')        video.style.cssText = 'position:absolute;top:-10000px;left:-10000px;'        // Event connected to stream        let loaded = false        video.onloadedmetadata = () => {            if (loaded) {                return            }            loaded = true            // Set video ORIGINAL height (screenshot)            video.style.height = video.videoHeight + 'px' // videoHeight            video.style.width = video.videoWidth + 'px' // videoWidth            // Create canvas            let canvas = document.createElement('canvas')            canvas.width = video.videoWidth            canvas.height = video.videoHeight            let ctx = canvas.getContext('2d')            // Draw video on canvas            ctx.drawImage(video, 0, 0, canvas.width, canvas.height)            if (this.callback) {                // Save screenshot to png - base64                this.callback(canvas.toDataURL('image/png'))            } else {                // console.log('Need callback!')            }            // Remove hidden video tag            video.remove()            try {                stream.getTracks()[0].stop()            } catch (e) {                // nothing            }        }        video.srcObject = stream        document.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, 不可为 false            fullscreen: 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, 不可为 false            fullscreen: 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)复制代码

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

转载地址:http://fluwa.baihongyu.com/

你可能感兴趣的文章
Android IT资讯网络阅读器应用源码
查看>>
Java基础学习总结(23)——GUI编程
查看>>
Ruby on Rails 环境搭建
查看>>
MyBatis学习总结(八)——Mybatis3.x与Spring4.x整合
查看>>
部署System Center App Controller 2012 Service Pack 1 (5)
查看>>
MySQL:日期函数、时间函数总结
查看>>
工作是什么
查看>>
Linux 中cpu通略
查看>>
服务器端创建账户收件箱规则--将邮件复制到指定文件夹中
查看>>
java中简单集合框架(二)
查看>>
函数返回局部变量的一些问题
查看>>
Solaris11性能监控--处理器
查看>>
内存模型
查看>>
如何快速开发网站?
查看>>
tomcat等服务器返回给页面的数字分别表示的意思!
查看>>
我的友情链接
查看>>
个人博客
查看>>
我的友情链接
查看>>
mysql 参数 innodb_flush_log_at_trx_commit
查看>>
Windows Server 2012 远程桌面,你需要具有通过远程桌面服务进行登录的权限
查看>>