1. 程式人生 > 其它 >入門Electron,手把手教你編寫完整實用案例

入門Electron,手把手教你編寫完整實用案例

Electron簡介

Electron是幹什麼的? 簡單來講,Electron 使用 JavaScript,HTML 和 CSS,來構建跨平臺的桌面應用程式。

按照官方的說法:如果你可以建一個網站,你就可以建一個桌面應用程式。

和傳統的桌面應用相比,使用Electron開發更容易上手,開發效率更高。並且,web技術應用廣泛、生態繁榮,Electron可以使用幾乎所有的Web生態領域及Node.js生態領域的元件和技術方案。

與網頁應用相比,Electron基於Chromium 和 Node.js,可以避免令人頭痛的瀏覽器相容問題。而Web前端受限訪問的檔案系統、系統托盤、系統通知等,開發Electron應用時可以自由地使用。

簡單理解Electron工作機制

使用Electron開發的桌面應用,類似於簡易版的、定製版的Chrome瀏覽器,當然這個瀏覽器中的頁面不能通過輸入網址開啟,而是由開發者寫好的。


和瀏覽器架構類似,Electron應用程式區分主程序和渲染程序。

主程序負責控制應用程式的生命週期、建立和管理應用程式視窗,有著多種控制原生桌面功能的模組,例如選單、對話方塊以及托盤圖示。

渲染程序負責完成渲染介面、接收使用者輸入、響應使用者的互動等工作。

一個Electron應用只有一個主程序,但可以有多個渲染程序。

在之前的文章中,我們有講過瀏覽器中的程序,可略作參考。

案例入門

下面我們將從一個任務管理的案例入門,瞭解electron的整體開發流程和一些基本的細節知識。

案例效果

功能分析:

1、記錄待完成任務和已完成任務

2、通過新建,新增待完成任務,並設定時間

3、點選完成任務,跳轉到已完成介面;點選刪除,可以刪除任務

4、點選右上角的×按鈕,可以關閉主介面,要再次開啟主介面,可以通過系統托盤

5、設定的時間到了,會在右下角彈出提醒框,如下圖所示。

初始化專案

專案是由原生js開發,在後面的文章中,我們會再探討electron和vue、react這些前端框架的結合。

mkdir tasky
cd tasky
npm init

安裝electron

npm install electron --S

建立一個hello world應用程式

(1)第一步,在專案根目錄下,建立index.js

,作為應用程式的入口檔案。因為Electron是基於Node.js,所以入口檔案使用Node.js語法。內容如下:

//引入兩個模組:app 和 BrowserWindow

//app 模組,控制整個應用程式的事件生命週期。
//BrowserWindow 模組,它建立和管理程式的視窗。

const { app, BrowserWindow } = require('electron')

//在 Electron 中,只有在 app 模組的 ready 事件被激發後才能建立瀏覽器視窗
app.on('ready', () => {

  //建立一個視窗
  const mainWindow = new BrowserWindow()
  
  //視窗載入html檔案
  mainWindow.loadFile('./src/main.html')
})

(2)第二步,建立視窗需要載入的html檔案。

為了方便後面的檔案管理,我們新建一個src資料夾,用於存放web頁面資源,比如html、css、js、圖片等。

// ./src/main.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  hello world
</body>
</html>

(3)啟動程式。

修改package.jsonscripts,如下:

然後在專案根目錄執行:npm run start

這樣我們的hello world應用程式就跑起來了。入門Electron,就是這麼簡單!

簡單的基礎除錯

1、主程序執行時的一些提示資訊會在命令列顯示,比如,在index.js加入console.log

app.on('ready', () => {
  console.log('just test console.log')
  const mainWindow = new BrowserWindow()
  mainWindow.loadFile('./src/main.html')
})

就可以在命令列看到列印的值:

index.js中的內容發生改變,預設要手動重啟,比較麻煩。這裡我們加入nodemon,它可以監控node.js原始碼的變化,並自動重啟應用。

安裝:npm i nodemon --D

修改scripts"start": "nodemon --watch index.js --exec electron ."

再次執行npm run start,當index.js內容變化時,就會自動重新執行electron .來重啟應用。

2、視窗頁面的除錯方法和chrome瀏覽器類似。點選選單欄的View --- Toggle Developer Tools,或者按它對應的快捷鍵,就會出現我們熟悉的開發者工具介面。

當頁面內容發生變化,可以點選View --- Reload,或者快捷鍵ctrl+r,重新整理頁面內容。頁面熱更新會後續講到。

開始coding

專案目錄結構如下,其中src資料夾存放的就是web頁面相關的內容。


專案有兩個視窗:主視窗和提醒視窗。在主視窗中管理任務,當任務設定時間到,會在螢幕右下角出現提醒視窗。

應用不涉及服務端資料,任務資料主要使用localStorage來儲存。

建立主視窗:

const iconPath = path.join(__dirname, './src/img/icon.png')   //應用執行時的標題欄圖示
let mainWindow
app.on('ready', () => {
  mainWindow = new BrowserWindow({
    resizable: false,   //不允許使用者改變視窗大小
    width: 800,        //設定視窗寬高
    height: 600,
    icon: iconPath,     //應用執行時的標題欄圖示
    webPreferences:{    
      backgroundThrottling: false,   //設定應用在後臺正常執行
      nodeIntegration:true,     //設定能在頁面使用nodejs的API
      contextIsolation: false,
      preload: path.join(__dirname, './preload.js')
    }
  })
  mainWindow.loadURL(`file://${__dirname}/src/main.html`)
}

main.html的內容很簡單,有興趣的童鞋可以去看原始碼,這裡就不貼了。

預設Electron應用頂部有標題欄和選單欄。縱觀各個桌面應用程式,基本都是定製的頂部控制區域。對於我們這個應用,暫時只要一個關閉按鈕,所以我們將去掉這一部分,將視窗關閉按鈕寫在main.html頁面中。

無邊框視窗

要建立無邊框視窗,只需在 BrowserWindow 的 options 中將 frame 設定為 false:

mainWindow = new BrowserWindow({
   frame: false,
   ...
})

標題欄和選單欄消失了,但也會有幾個問題:

1、雖然選單欄消失了,但是依然可以通過快捷鍵進行選單操作,比如ctrl+shift+i開啟開發者工具,為避免這種情況,我們需要去掉選單欄:

mainWindow.removeMenu()

2、預設情況下,無邊框視窗是不可拖拽的。應用程式需要在 CSS 中指定 -webkit-app-region: drag 來告訴 Electron 哪些區域是可拖拽的。

html,body {
  height: 100%;
  width: 100%;
}

body{
  -webkit-app-region: drag;
}

如果用上面的屬性使整個視窗都可拖拽,則必須將其中的按鈕標記為不可拖拽,否則按鈕將無法點選。

.enable-click {
  -webkit-app-region: no-drag;
}

3、當點選自定義的視窗關閉按鈕,我們並不希望退出程式,只是將視窗隱藏,可以通過系統托盤再次開啟視窗。

系統托盤

程式啟動時,將應用程式加入系統托盤。在Electron中,藉助Tray模組實現。

const { app, BrowserWindow, Tray, Menu } = electron
const iconPath = path.join(__dirname, './src/img/icon.png')
let mainWindow, tray
app.on('ready', () => {
  mainWindow = new BrowserWindow({
    //... options
  })
  mainWindow.loadURL(`file://${__dirname}/src/main.html`)
  
  tray = new Tray(iconPath)      //例項化一個tray物件,建構函式的唯一引數是需要在托盤中顯示的圖示url  
  
  tray.setToolTip('Tasky')       //滑鼠移到托盤中應用程式的圖示上時,顯示的文字
  
  tray.on('click', () => {       //點選圖示的響應事件,這裡是切換主視窗的顯示和隱藏
    if(mainWindow.isVisible()){
      mainWindow.hide()
    }else{
      mainWindow.show()
    }
  })
  
  tray.on('right-click', () => {    //右鍵點選圖示時,出現的選單,通過Menu.buildFromTemplate定製,這裡只包含退出程式的選項。
    const menuConfig = Menu.buildFromTemplate([
      {
        label: 'Quit',
        click: () => app.quit()
      }
    ])
    tray.popUpContextMenu(menuConfig)
  })

})

IPC通訊

回到上一個問題。點選頁面內的按鈕怎樣隱藏視窗?這就需要用到IPC通訊了。


IPC(Inter-Process Communication),就是程序間通訊。Electron應用程式區分主程序和渲染程序,有時候,兩者之間需要通訊,傳輸一些資料、傳送一些訊息。

渲染程序 TO 主程序

比如,點選關閉按鈕,就需要渲染程序向主程序傳送隱藏主視窗的請求。

渲染程序使用Electron內建的ipcRenderer模組向主程序傳送訊息,ipcRenderer.send方法的第一個引數是訊息管道名稱。

//頁面的js程式碼:
const electron = require('electron')
const { ipcRenderer } = electron

closeDom.addEventListener('click', () => {
  ipcRenderer.send('mainWindow:close')
})

主程序通過ipcMain接收訊息,ipcMain.on方法的第一個引數也為訊息管道的名稱,與ipcRenderer.send的名稱對應,第二個引數是接收到訊息的回撥函式。

//入口檔案index.js
ipcMain.on('mainWindow:close', () => {
  mainWindow.hide()
})

主程序 TO 渲染程序

主程序向渲染程序傳送訊息是通過渲染程序的webContents。在mainWindow渲染程序設定了任務後,會傳輸給主程序任務資訊,當任務時間到了,主程序會建立提醒視窗remindWindow,並通過remindWindow.webContents將任務名稱發給remindWindow

function createRemindWindow (task) {
 
  remindWindow = new BrowserWindow({
     //options
  })
  remindWindow.loadURL(`file://${__dirname}/src/remind.html`)
  
  //主程序傳送訊息給渲染程序
  remindWindow.webContents.send('setTask', task)

}

remindWindow渲染程序中,通過ipcRenderer.on接受訊息。

ipcRenderer.on('setTask', (event,task) => {
   document.querySelector('.reminder').innerHTML = 
      `<span>${decodeURIComponent(task)}</span>的時間到啦!`
})

渲染程序 TO 渲染程序

渲染程序之間傳遞訊息,可以通過主程序中轉,即視窗A先把訊息傳送給主程序,主程序再把這個訊息傳送給視窗B,這種非常常見。

也可以從視窗A直接發訊息給視窗B,前提是視窗A知道視窗B的webContents的id。

ipcRenderer.sendTo(webContentsId, channel, ...args)

值得注意的是,我們在頁面的js程式碼中使用了require,這也是Electron的一大特點,在渲染程序中可以訪問Node.js API。這樣做的前提是在建立視窗時配置webPreferencesnodeIntegration: truecontextIsolation: false

mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences:{
      nodeIntegration: true,
      contextIsolation: false
    }
  })

視窗位置

當任務時間到,提醒視窗會在螢幕右下角出現。怎樣設定視窗位置呢?

function createRemindWindow (task) {
  //建立提醒視窗
  remindWindow = new BrowserWindow({
    //...options
  })
  
  //獲取螢幕尺寸
  const size = screen.getPrimaryDisplay().workAreaSize
  
  //獲取托盤位置的y座標(windows在右下角,Mac在右上角)
  const { y } = tray.getBounds()
  
  //獲取視窗的寬高
  const { height, width } = remindWindow.getBounds()
  
  //計算視窗的y座標
  const yPosition = process.platform === 'darwin' ? y : y - height
  
  //setBounds設定視窗的位置
  remindWindow.setBounds({
    x: size.width - width,     //x座標為螢幕寬度 - 視窗寬度
    y: yPosition,
    height,
    width 
  })
  
  //當有多個應用時,提醒視窗始終處於最上層
  remindWindow.setAlwaysOnTop(true)
  
  remindWindow.loadURL(`file://${__dirname}/src/remind.html`)
}

關閉視窗

提醒視窗會在一段時間後關閉,可以通過remindWindow.close()來關閉視窗。

當視窗關閉後,我們可以設定remindWindow = null來回收分配給該渲染程序的資源。

remindWindow.on('closed', () => { remindWindow = null })

結語

這篇文章主要是介紹electron的一些基礎知識,下一篇文章,我們將探討electron的打包問題,下次見。

凡能用JavaScript實現的,註定會被用JavaScript實現。        
                                               ---Jeff Atwood