上一篇文章《從零開始用 electron 手擼一個截屏工具》發佈以後發現閱讀的朋友還很多,不過工具真正使用的時候就發現了問題,因此爲了讓咱們的截圖工具更好用,就又作了不少優化,固然了也遇到了不少坑。css
截屏效果圖: html
項目修改後的完整代碼依然是以前的地址: github.com/chrisbing/e… 歡迎你們關注git
接下來就列舉一下解決的問題和具體作法github
先放上一版截圖代碼web
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秒以上,並且鼠標還會出現等待的樣式,這個體驗是至關差了chrome
因此就須要尋求替代方案了,參考 github.com/electron/el… 和 github.com/electron/el… 這兩個 Issue,替代方案有兩種,第一種用第三方原生的一些截屏程序,第二種是利用getUserMedia
canvas
我選了第二種方法,主要是以爲簡單吧。第一種方法你們能夠嘗試一下,也歡迎反饋結果。windows
下面附上修改後的代碼app
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。electron
修改後截屏不會出現整個程序掛起的狀況,時間也縮小到600ms 左右,這個時間對於截圖來講已是能夠接受的了。
當電腦有多個顯示器的狀況,多屏截圖就很重要了,以前只提到了一個屏幕的狀況,那多屏應該怎麼處理呢?
因爲全屏狀況,窗口只能佔據一個屏幕,因此多屏截圖只能用多個截屏窗口來處理了(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()
}
}
})
複製代碼
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)
複製代碼
通過上面的優化後,這個截圖工具已經能夠達到產品級了。固然還有一些不足的地方,好比跨屏截圖,塗鴉,各類各樣的體驗細節吧,後面有時間優化完,再來和你們分享!!!