用electron開發了一個屏幕截圖工具

前段時間作了一個釘釘的Linux版本,因爲是基於網頁版作的,因此缺失了不少桌面應用程序的功能。因爲使用的用戶可能是Linux的用戶,因此在Linux的截圖功能沒有,在幾個用戶的要求下決定作一個截圖功能。javascript

項目目前支持顯示器截圖,在windows上運行效果比較理想,Linux上有必定的BUG,目前還不可以支持跨屏幕截圖(一個截圖橫跨兩個顯示器)功能,本文也發佈在了簡書,也能夠去簡書閱讀,傳送門www.jianshu.com/p/276a29b28…html

electron截圖API

在electron中提供了desktopCapturer模塊,該模塊只能在渲染進程使用。
該模塊只提供了一個方法desktopCapturer.getSources(options, callback)java

  • 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: 屏幕縮略圖

屏幕截圖功能編寫

  1. 爲了可以符合大多數用戶的習慣,特別是用慣了QQ截圖功能的小夥伴,因此使用了快捷鍵ctrl+alt+a來截圖
  2. 全部的程序處理代碼都必須等到app ready事件以後再處理,不然會報錯,因此全部代碼都放到了ready事件的回調函數中。
  3. 爲了把截圖功能給獨立出來不與其餘模塊相互干擾,因此就把截圖相關的主進程代碼單獨寫到文件shortcut-capture.js,並把模塊封裝爲一個函數,而且經過變量控制,保證整個應用進程內只會執行一次初始化截圖模塊。
  4. 主進程代碼以下git

    // 引入各個模塊
     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
     }複製代碼
  5. 主進程與渲染進程通訊經過ipcMain模塊完成,ipcMain經過監聽渲染進程傳過來的事件得到渲染進程的數據,而且兩個進程通訊數據只能是簡單對象。主進程向渲染進程傳遞數據是經過webContents的send方法實現的,渲染進程經過ipcRender對象事件監聽實現,同是主進程也能夠經過webContents.executeJavaScript方法以字符串的方式向頁面注入js進行執行。
  6. 當程序運行以後,當用戶按下快捷鍵後,主窗口的渲染進程就開始截圖,截圖後就把數據傳到主進程,而後主進程建立新窗口,並把截圖數據傳遞到新建立的窗口中,而後等待用戶的截圖操做
    // 主進程捕獲到截圖快捷鍵就讓渲染進程截圖
     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))
     })複製代碼
  7. 在本項目就採用了webContents.executeJavaScript的方法向頁面傳遞了截圖數據的
  8. 渲染進程接收到主進程的dom-ready事件以後就開始繪製截圖界面,並把頁面拖拽截取圖片功能初始化。當用戶按下ESC按鍵的時候就關閉截圖窗口退出截屏
  9. 圖片裁剪功能。圖片裁剪是利用了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的
  10. 截取玩圖片以後點擊截圖工具欄的肯定按鈕,而後就會從canvas讀取圖片信息,而後轉換爲dataURL傳到主進程,主進程就把圖片數據寫入到剪切板並關閉窗口
  11. 因爲截圖窗口渲染進程的代碼較多,這裏就不上了,能夠在Github上查看,下附整個截圖的流程關係
    示意圖.png
    示意圖.png

最後,若是有時間的話,可也在考慮能夠把截圖這個功能單獨提取出來而後作成一個模塊,可以在其餘electron項目中直接引用便可。寫得很差的地方請各位大佬包容,GitHub項目地址:github.com/nashaofu/di…github

相關文章
相關標籤/搜索