前段時間做了一個釘釘的Linux版本,由於是基於網頁版做的,所以缺失了很多桌面應用程式的功能。由於使用的使用者多是Linux的使用者,所以在Linux的截圖功能沒有,在幾個使用者的要求下決定做一個截圖功能。
專案目前支援顯示器截圖,在windows上執行效果比較理想,Linux上有一定的BUG,目前還不能夠支援跨螢幕截圖(一個截圖橫跨兩個顯示器)功能,本文也釋出在了簡書,也可以去簡書閱讀,傳送門www.jianshu.com/p/276a29b28…
electron截圖API
在electron中提供了desktopCapturer模組,該模組只能在渲染程式使用。
該模組只提供了一個方法desktopCapturer.getSources(options, callback)
:
- options是一個物件,其中包含兩個引數
- types: 一個 String 陣列,列出了可以捕獲的桌面資源型別, 可用型別為 screen 和 window.
- thumbnailSize (可選) :建議縮略可被縮放的 size, 預設為 {width: 150, height: 150}.
- callback(error, sources)是一個回撥函式,其中會傳遞兩個引數:
- error: 獲取截圖失敗時的錯誤資訊
- sources: 是一個 Source 物件陣列, 每個 Source 表示了一個捕獲的螢幕或單獨視窗,並且有如下屬性
- id: 在 navigator.webkitGetUserMedia中使用的捕獲視窗或螢幕的id,格式為 window:XX或者screen:XX,XX是一個隨機數
- name:捕獲視窗或螢幕的描述名,如果資源為螢幕,名字為Entire Screen或Screen ; 如果資源為視窗, 名字為視窗的標題
- thumbnail: 螢幕縮圖
螢幕截圖功能編寫
- 為了能夠符合大多數使用者的習慣,特別是用慣了QQ截圖功能的小夥伴,所以使用了快捷鍵
ctrl+alt+a
來截圖 - 所有的程式處理程式碼都必須等到app ready事件之後再處理,否則會報錯,所以所有程式碼都放到了ready事件的回撥函式中。
- 為了把截圖功能給獨立出來不與其他模組相互干擾,所以就把截圖相關的主程式程式碼單獨寫到檔案shortcut-capture.js,並把模組封裝為一個函式,並且通過變數控制,保證整個應用程式內只會執行一次初始化截圖模組。
主程式程式碼如下
// 引入各個模組 const { globalShortcut, ipcMain, BrowserWindow, clipboard, nativeImage } = require('electron') // 保證函式只執行一次 let isRuned = false // 截圖時會出現截圖介面,如下就是儲存截圖視窗的陣列 const $windows = [] // 判斷是否為快捷鍵退出,其他的退出方式都不被允許 let isClose = false module.exports = mainWindow => { if (isRuned) { return } isRuned = true // 註冊全域性快捷鍵 globalShortcut.register('ctrl+alt+a', function () { mainWindow.webContents.send('shortcut-capture') }) // 抓取截圖之後顯示視窗 ipcMain.on('shortcut-capture', (e, sources) => { // 如果有以前的視窗就關閉以前的視窗 // 然後根據截圖資源於螢幕資料生成視窗 closeWindow() sources.forEach(source => { createWindow(source) }) }) // 有一個視窗關閉就關閉所有的視窗 ipcMain.on('cancel-shortcut-capture', closeWindow) // 截圖視窗確認截圖時把資料傳遞到主程式 // 然後把資料寫入到剪下板,並關閉視窗 // 沒有直接在渲染程式把資料寫入剪下板是因為在Linux上會報錯 // 所以就把這一步改到主程式完成 ipcMain.on('set-shortcut-capture', (e, dataURL) => { clipboard.writeImage(nativeImage.createFromDataURL(dataURL)) closeWindow() }) } // 建立視窗 function createWindow (source) { // display為螢幕相關資訊 // 特別再多螢幕的時候要定位各個視窗到對應的螢幕 const { display } = source const $win = new BrowserWindow({ title: '截圖', width: display.size.width, height: display.size.height, x: display.bounds.x, y: display.bounds.y, frame: false, show: false, transparent: true, resizable: false, alwaysOnTop: true, fullscreen: true, skipTaskbar: true, closable: true, minimizable: false, maximizable: false }) // 全屏視窗 setFullScreen($win, display) // 只能通過cancel-shortcut-capture的方式關閉視窗 $win.on('close', e => { if (!isClose) { e.preventDefault() } }) // 頁面初始化完成之後再顯示視窗 // 並檢測是否有版本更新 $win.once('ready-to-show', () => { $win.show() $win.focus() // 重新調整視窗位置和大小 setFullScreen($win, display) }) // 當頁面載入完成時通知截圖視窗開始程式的執行 $win.webContents.on('dom-ready', () => { $win.webContents.executeJavaScript(`window.source = ${JSON.stringify(source)}`) $win.webContents.send('dom-ready') $win.focus() }) // 載入地址 $win.loadURL(`file://${__dirname}/window/shortcut-capture.html`) $windows.push($win) } // 讓視窗全屏 function setFullScreen ($win, display) { $win.setBounds({ width: display.size.width, height: display.size.height, x: display.bounds.x, y: display.bounds.y }) $win.setAlwaysOnTop(true) $win.setFullScreen(true) } // 關閉視窗 function closeWindow () { isClose = true while ($windows.length) { const $winItem = $windows.pop() $winItem.close() } isClose = false }複製程式碼
- 主程式與渲染程式通訊通過ipcMain模組完成,ipcMain通過監聽渲染程式傳過來的事件獲得渲染程式的資料,並且兩個程式通訊資料只能是簡單物件。主程式向渲染程式傳遞資料是通過webContents的send方法實現的,渲染程式通過ipcRender物件事件監聽實現,同是主程式也可以通過
webContents.executeJavaScript
方法以字串的方式向頁面注入js進行執行。 - 當程式執行之後,當使用者按下快捷鍵後,主視窗的渲染程式就開始截圖,截圖後就把資料傳到主程式,然後主程式建立新視窗,並把截圖資料傳遞到新建立的視窗中,然後等待使用者的截圖操作
// 主程式捕獲到截圖快捷鍵就讓渲染程式截圖 ipcRenderer.on('shortcut-capture', () => { // 獲取螢幕數量 // screen為electron的模組 const displays = screen.getAllDisplays() // 每個螢幕都截圖一個 // desktopCapturer.getSources可以一次獲取所有桌面的截圖 // 但由於thumbnailSize不一樣所以就採用了每個桌面尺寸都捕獲一張 const getDesktopCapturer = displays.map((display, i) => { return new Promise((resolve, reject) => { desktopCapturer.getSources({ types: ['screen'], thumbnailSize: display.size }, (error, sources) => { if (!error) { return resolve({ display, thumbnail: sources[i].thumbnail.toDataURL() }) } return reject(error) }) }) }) Promise.all(getDesktopCapturer) .then(sources => { // 把資料傳遞到主程式 ipcRenderer.send('shortcut-capture', sources) }) .catch(error => console.log(error)) })複製程式碼
- 在本專案就採用了
webContents.executeJavaScript
的方法向頁面傳遞了截圖資料的 - 渲染程式接收到主程式的dom-ready事件之後就開始繪製截圖介面,並把頁面拖拽擷取圖片功能初始化。當使用者按下
ESC
按鍵的時候就關閉截圖視窗退出截圖 - 圖片裁剪功能。圖片裁剪是利用了canvas來實現的。canvas可以根據一張圖片來繪製出圖形,然後利用canvas的api把繪製出來的圖片給獲取成為可用的圖片資源,然後提交給主程式。其中主要利用了canvas的
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
方法- 其中image為圖片資源
- sx、sy為原始圖片資源要繪製的開始的位置
- sWidth、sHeight為原始圖片資源要繪製大小
- dx、dy為把圖片繪製到畫布的起始位置
- dWidth、dHeight為把圖片在畫布上繪製的大小
- 本專案中sx、sy、 sWidth、sHeight都為擷取的區域大小和區域相對於視窗左上角的座標位置,dx、dy都為0,表示從畫布的左上角開始繪製,dWidth、dHeight為擷取區域大小,如果dWidth、dHeight和sWidth、sHeight不相等就可以實現擷取區域的縮放,但本專案是1:1的
- 擷取玩圖片之後點選截圖工具欄的確定按鈕,然後就會從canvas讀取圖片資訊,然後轉換為dataURL傳到主程式,主程式就把圖片資料寫入到剪下板並關閉視窗
- 由於截圖視窗渲染程式的程式碼較多,這裡就不上了,可以在Github上檢視,下附整個截圖的流程關係
最後,如果有時間的話,可也在考慮可以把截圖這個功能單獨提取出來然後做成一個模組,能夠在其他electron專案中直接引用即可。寫得不好的地方請各位大佬包容,GitHub專案地址:github.com/nashaofu/di…