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


Electron簡介

Electron是干什么的? 簡單來講,Electron 使用 JavaScript,HTML 和 CSS,來構建跨平台的桌面應用程序。

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

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

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

簡單理解Electron工作機制

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

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

主進程負責控制應用程序的生命周期、創建和管理應用程序窗口,有着多種控制原生桌面功能的模塊,例如菜單、對話框以及托盤圖標。

渲染進程負責完成渲染界面、接收用戶輸入、響應用戶的交互等工作。

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

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

案例入門

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

案例效果

mainWindow.gif

功能分析:

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

2、通過新建,添加待完成任務,並設置時間

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

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

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

image.png

初始化項目

項目是由原生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,如下:

image.png

然后在項目根目錄運行:npm run start

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

image.png

簡單的基礎調試

1、主進程運行時的一些提示信息會在命令行顯示,比如,在index.js加入console.log

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

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

image.png

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,或者按它對應的快捷鍵,就會出現我們熟悉的開發者工具界面。

image.png

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

開始coding

項目目錄結構如下,其中src文件夾存放的就是web頁面相關的內容。

image.png
項目有兩個窗口:主窗口和提醒窗口。在主窗口中管理任務,當任務設定時間到,會在屏幕右下角出現提醒窗口。

應用不涉及服務端數據,任務數據主要使用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的內容很簡單,有興趣的童鞋可以去看源碼,這里就不貼了。

image.png

默認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、當點擊自定義的窗口關閉按鈕,我們並不希望退出程序,只是將窗口隱藏,可以通過系統托盤再次打開窗口。
image.png

系統托盤

程序啟動時,將應用程序加入系統托盤。在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通信了。

image.png
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>的時間到啦!`
})

image.png

渲染進程 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM