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.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