入門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
//引入兩個模組: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.json
的scripts
,如下:
然後在專案根目錄執行: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。這樣做的前提是在建立視窗時配置webPreferences
的nodeIntegration: true
和contextIsolation: 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