一、前言
NW.js
和Electron
都可以用前端的知識來開發桌面應用。NW.js
和Electron
起初是同一 個作者開發。后來種種原因分為兩個產品。一個命名為NW.js
(英特爾公司提供技術支持)、 另一命名為Electron
(Github 公司提供技術支持)。NW.js
和Electron
可以用Nodejs
中幾乎所有的模塊。NW.js
和Electron
不僅可以把html
寫的web
頁面打包成跨平台可以安裝到電腦上面的軟件,也可以通過javascript
訪問操作 系統原生的UI
和Api
(控制窗口、添加菜單項目、托盤應用菜單、讀寫文件、訪問剪貼板)。
github
的atom
編輯器、微軟的vscode
編輯器,包括阿里內部的一些 軟件也是用electron
開發的
1. Electron 是由誰開發的?
Electron
是由Github
開發
2. Electron 是什么?
Electron
是一個用HTML
,CSS
和JavaScript
來構建跨平台桌面應用程序的一個開源庫
3. Electron 把 HTML,CSS 和 JavaScript 組合的程序構建為跨平台桌面應用程序的原理 是什么?
原理為
Electron
通過將Chromium
和Node.js
合並到同一個運行時環境中,並將其打包為Mac
,Windows
和Linux
系統下的應用來實現這一目的。
4. Electron 何時出現的,為什么會出現?
Electron
於2013
年作為構建Atom
的框架而被開發出來。這兩個項目在2014
春季開源。 (Atom:為 Github 上可編程的文本編輯器)
一些歷史:
2013
年4
月Atom Shell
項目啟動 。2014
年5
月Atom Shell
被開源 。2015
年4
月Atom Shell
被重命名為Electron
2016
年5
月Electron
發布了v1.0.0
版本
5. Electron 當前流行程度?
目前
Electron
已成為開源開發者、初創企業和老牌公司常用的開發工具。
6. Electron 當前由那些人在維護支持?
Electron
當前由Github
上的一支團隊和一群活躍的貢獻者維護。有些貢獻者是獨立開發者,有些則在用Electron
構建應用的大型公司里工作。
7. Electron 新版本多久發布一次?
Electron
的版本發布相當頻繁。每當Chromium
、Node.js
有重要的bug
修復,新API
或是版本更新時Electron
會發布新版本。
- 一般
Chromium
發行新的穩定版后的一到兩周之內,Electron
中Chromium
的版本會對其進行更新,具體時間根據升級所需的工作量而定。
一般Node.js
發行新的穩定版一個月后,Electron
中Node.js
的版本會對其進行更新,具 體時間根據升級所需的工作量而定。
8. Electron 的核心理念是什么?
Electron
的核心理念是:保持Electron
的體積小和可持續性開發。
如:為了保持Electron
的小巧 (文件體積) 和可持續性開發 (以防依賴庫和API
的泛濫) ,Electron
限制了所使用的核心項目的數量。
比如Electron
只用了Chromium
的渲染庫而不是其全部組件。這使得升級Chromium
更加容易,但也意味着Electron
缺少了Google Chrome
里的一些瀏覽器相關的特性。 添加到Electron
的新功能應該主要是原生API
。 如果可以的話,一個功能應該盡可能的成 為一個Node.js
模塊。
9. Electron 當前的最新版本為多少?
Electron
當前的最新版本為4.0.1
(當前時間為2019
年1
月6
號)
二、環境搭建
1. 安裝 electron
npm install -g electron
2. 克隆一個倉庫、快速啟動一個項目
# 克隆示例項目的倉庫 git clone https://github.com/electron/electron-quick-start # 進入這個倉庫 cd electron-quick-start # 安裝依賴並運行 npm install && npm start
3. 手動搭建一個 electron 項目
- 新建一個項目目錄 例如:
electrondemo01
- 在
electrondemo01
目錄下面新建三個文件:index.html
、main.js
、package.json
index.html
里面用css
進行布局(以前怎么寫現在還是怎么寫)- 在
main.js
中寫如下代碼
var electron =require('electron'); //electron 對象的引用 const app=electron.app; //BrowserWindow 類的引用 const BrowserWindow=electron.BrowserWindow; let mainWindow=null; //監聽應用准備完成的事件 app.on('ready',function(){ //監聽應用准備完成的事件 app.on('ready',function(){ //創建窗口 mainWindow=new BrowserWindow({width: 800, height: 600}); mainWindow.loadFile('index.html'); mainWindow.on('closed', function () { mainWindow = null; }) }) }) //監聽所有窗口關閉的事件 app.on('window-all-closed', function () { // On OS X it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== 'darwin') { app.quit(); } })
- 運行
electron . #注意:命令后面有個點
4. electron-forge 搭建一個 electron 項目
electron-forge
相當於electron
的一個腳手架,可以讓我們更方便的創建、運行、打包electron
項目
npm install -g electron-forge electron-forge init my-new-app cd my-new-app npm start
三、Electron 運行流程
3.1 Electron 運行的流程

3.2 Electron 主進程和渲染進程
Electron
運行package.json
的main
腳本的進程被稱為主進程。- 在主進程中運行的腳本通過創建
web
頁面來展示用戶界面。 一個Electron
應用總是有且只有一個主進程。 - 由於
Electron
使用了Chromium
(谷歌瀏覽器)來展示web
頁面,所以Chromium
的 多進程架構也被使用到。 每個Electron
中的web
頁面運行在它自己的渲染進程中。 - 主進程使用
BrowserWindow
實例創建頁面。每個BrowserWindow
實例都在自己的渲 染進程里運行頁面。 當一個BrowserWindow
實例被銷毀后,相應的渲染進程也會被終止


- 進程:進程是計算機中的程序關於某數據集合上的一次運行活動,是 系統進行資源分配和調度的基本單位,是操作系統結構的基礎。
- 線程:在一個程序里的一個執行路線就叫做線程(
thread
)。更准確的定義是: 線程是“一個進程內部的控制序列”。 - 線程和進程:一個程序至少有一個進程,一個進程至少有一個線程
3.3 Electron 渲染進程中通過 Nodejs 讀取本地文件
在普通的瀏覽器中,
web
頁面通常在一個沙盒環境中運行,不被允許去接觸原生的資源。 然而Electron
的用戶在Node.js
的API
支持下可以在頁面中和操作系統進行一些底層交 互。
Nodejs
在主進程和渲染進程中都可以使用。渲染進程因為安全限制,不能直接操作生GUI
。雖然如此,因為集成了 Nodejs,渲染進程也有了操作系統底層API
的能力,Nodejs
中常用的Path
、fs
、Crypto
等模塊在Electron
可以直接使用,方便我們處理鏈接、路徑、 文件MD5
等,同時npm
還有成千上萬的模塊供我們選擇。
var fs = require('fs'); var content = document.getElementById('content'); var button = document.getElementById('button'); button.addEventListener('click',function(e){ fs.readFile('package.json','utf8',function(err,data){ content.textContent = data; console.log(data); }); });
3.4 Electron 開啟調試模式
mainWindow.webContents.openDevTools();

四、Electron 模塊介紹
Electron
模塊介紹、remote
模塊、通 過BrowserWindow
打開新窗口
4.1 Electron 主進程和渲染進程中的模塊

4.2 Electron remote 模塊
remote
模塊提供了一種在渲染進程(網頁)和主進程之間進行進程間通訊(IPC
)的簡便途徑
Electron
中, 與GUI
相關的模塊(如dialog
,menu
等)只存在於主進程,而不在渲染進程中 。為了能從渲染進程中使用它們,需要用ipc
模塊來給主進程發送進程間消息。使用remote
模塊,可以調用主進程對象的方法,而無需顯式地發送進程間消息,這類似於Java
的RMI
4.3 通過BrowserWindow 打開新窗口
Electron
渲染進程中通過remote
模塊調用主進程中的BrowserWindow
打開新窗口
// 主進程代碼
const electron = require('electron'); // 控制應用生命周期的模塊 const {app} = electron; // 創建本地瀏覽器窗口的模塊 const {BrowserWindow} = electron; // 指向窗口對象的一個全局引用,如果沒有這個引用,那么當該 javascript 對象被垃圾回收 的 // 時候該窗口將會自動關閉 let win; function createWindow() { // 創建一個新的瀏覽器窗口 win = new BrowserWindow({width: 1104, height: 620});//570+50 // 並且裝載應用的 index.html 頁面 win.loadURL(`file://${__dirname}/html/index.html`); // 打開開發工具頁面 win.webContents.openDevTools(); //當窗口關閉時調用的方法 win.on('closed', () => { // 解除窗口對象的引用,通常而言如果應用支持多個窗口的話,你會在一個數組里 // 存放窗口對象,在窗口關閉的時候應當刪除相應的元素。 win = null; }); } // 當 Electron 完成初始化並且已經創建了瀏覽器窗口,則該方法將會被調用。 // 有些 API 只能在該事件發生后才能被使用 app.on('ready', createWindow); // 當所有的窗口被關閉后退出應用 app.on('window-all-closed', () => { // 對於 OS X 系統,應用和相應的菜單欄會一直激活直到用戶通過 Cmd + Q 顯式退出 if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', () => { // 對於 OS X 系統,當 dock 圖標被點擊后會重新創建一個 app 窗口,並且不會有其他 // 窗口打開 if (win === null) { createWindow(); } }); // 在這個文件后面你可以直接包含你應用特定的由主進程運行的代碼。 // 也可以把這些代碼放在另一個文件中然后在這里導入 // 渲染進程代碼 /src/render/index.js // 打開新窗口屬性用法有點類似vscode打開新的窗口 const btn = document.querySelector('#btn'); const path = require('path'); const BrowerWindow = require('electron').remote.BrowserWindow; btn.onclick = () => { win = new BrowerWindow({ width: 300, height: 200, frame: false, // false隱藏關閉按鈕、菜單選項 true顯示 fullscreen:true, // 全屏展示 transparent: true }) win.loadURL(path.join('file:',__dirname,'news.html')); win.on('close',()=>{win = null}); }

五、自定義頂部菜單/右鍵菜單

5.1 主進程中調用Menu模塊-自定義軟件頂部菜單
Electron
中Menu
模塊可以用來創建原生菜單,它可用作應用菜單和context
菜單
這個模塊是一個主進程的模塊,並且可以通過
remote
模塊給渲染進程調用
// main/menu.js const { Menu } = require('electron') // 文檔 https://electronjs.org/docs/api/menu-item // 菜單項目 let menus = [ { label: '文件', submenu: [ { label: '新建文件', accelerator: 'ctrl+n', // 綁定快捷鍵 click: function () { // 綁定事件 console.log('新建文件') } }, { label: '新建窗口', click: function () { console.log('新建窗口') } } ] }, { label: '編輯', submenu: [ { label: '復制', role: 'copy' // 調用內置角色實現對應功能 }, { label: '剪切', role: 'cut' // 調用內置角色實現對應功能 } ] }, { label: '視圖', submenu: [ { label: '瀏覽' }, { label: '搜索' } ] } ] let m = Menu.buildFromTemplate(menus) Menu.setApplicationMenu(m) // 在主進程src/index.js中引入 const createWindow = () => { // 創建菜單 // 引入菜單模塊 require('./main/menu.js') };

我們給菜單綁定事件,在命令行控制台可以看到

5.2 渲染進程中調用Menu模塊
不推薦使用這種方式,建議在主進程中使用
1. remote
通過
remote
調用主進程的方法
// 菜單引入的方式發生變化 const { Menu } = require('electron').remote // 其他代碼和上面菜單一樣 // ...
2. 加入index.html
<script src="render/menu.js"></script>
5.3 渲染進程中自定義右鍵菜單
1. 定義菜單
// render/menu.js // 在渲染進程中通過remote模塊調用主進程中的模塊 const { Menu } = require('electron').remote const { remote } = require('electron') // 文檔 https://electronjs.org/docs/api/menu-item // 菜單項目 let menus = [ { label: '文件', submenu: [ { label: '新建文件', accelerator: 'ctrl+n', // 綁定快捷鍵 click: function () { // 綁定事件 console.log('新建文件') } }, { label: '新建窗口', click: function () { console.log('新建窗口') } } ] }, { label: '編輯', submenu: [ { label: '復制', role: 'copy' // 調用內置角色實現對應功能 }, { label: '剪切', role: 'cut' // 調用內置角色實現對應功能 } ] }, { label: '視圖', submenu: [ { label: '瀏覽' }, { label: '搜索' } ] } ] let m = Menu.buildFromTemplate(menus) // Menu.setApplicationMenu(m) // 綁定右鍵菜單 window.addEventListener('contextmenu', (e)=>{ e.preventDefault() m.popup({ window: remote.getCurrentWindow() }) }, false)

2. 引入
<!--index.html--> <script src="render/menu.js"></script>
六、進程通信

6.1 主進程與渲染進程之間的通信
有時候我們想在渲染進程中通過一個事件去執行主進程里面的方法。或者在渲染進程中通知 主進程處理事件,主進程處理完成后廣播一個事件讓渲染進程去處理一些事情。這個時候就 用到了主進程和渲染進程之間的相互通信
Electron
主進程,和渲染進程的通信主要用到兩個模塊:ipcMain
和ipcRenderer
ipcMain
:當在主進程中使用時,它處理從渲染器進程(網頁)發送出來的異步和同步信息,當然也有可能從主進程向渲染進程發送消息。ipcRenderer
: 使用它提供的一些方法從渲染進程 (web
頁面) 發送同步或異步的消息到主進程。 也可以接收主進程回復的消息
6.1.1 渲染進程給主進程發送異步消息
間接實現渲染進程執行主進程里面的方法
1. 引入ipcRender
<!--src/index.html--> <button id="send">在 渲染進程中執行主進程里的方法(異步)</button> <script src="render/ipcRender.js"></script>
2. 引入ipcMain
// 在主進程src/index.js中引入 const createWindow = () => { // 創建菜單 // 引入菜單模塊 require('./main/ipcMain.js') };
3. 渲染進程發送消息
// src/render/ipcRender.js //渲染進程 let send = document.querySelector('#send'); const { ipcRenderer } = require('electron'); send.onclick = function () { // 傳遞消息給主進程 // 異步 ipcRenderer.send('sendMsg', {name:'poetries', age: 23}) }
2. 主進程接收消息
// src/main/ipcMain.js //主進程 const { ipcMain } = require('electron') // 主進程處理渲染進程廣播數據 ipcMain.on('sendMsg', (event, data)=> { console.log('data\n ', data) console.log('event\n ', event) })

6.1.2 渲染進程發送消息,主進程接收消息並反饋
渲染進程給主進程發送異步消息,主進程接收到異步消息以后通知渲染進程
1. 引入ipcRender
<!--src/index.html--> <button id="sendFeedback">在 渲染進程中執行主進程里的方法,並反饋給主進程(異步)</button> <script src="render/ipcRender.js"></script>
2. 引入ipcMain
// 在主進程src/index.js中引入 const createWindow = () => { // 創建菜單 // 引入菜單模塊 require('./main/ipcMain.js') };
3. 渲染進程發送消息
// src/render/ipcRender.js //渲染進程 let sendFeedback = document.querySelector('#sendFeedback'); const { ipcRenderer } = require('electron'); // 向主進程發送消息 sendFeedback.onclick = function () { // 觸發主進程里面的方法 ipcRenderer.send('sendFeedback', {name:'poetries', age: 23}) }
4. 主進程收到消息處理並廣播反饋通知渲染進程
// src/main/ipcMain.js //主進程 const { ipcMain } = require('electron') // 主進程處理渲染進程廣播數據,並反饋給渲染進程 ipcMain.on('sendFeedback', (event, data)=> { // console.log('data\n ', data) // console.log('event\n ', event) // 主進程給渲染進程廣播數據 event.sender.send('sendFeedbackToRender', '來自主進程的反饋') })
5. 渲染進程處理主進程廣播的數據
// src/render/ipcRender.js // 向主進程發送消息后,接收主進程廣播的事件 ipcRenderer.on('sendFeedbackToRender', (e, data)=>{ console.log('event\n ', e) console.log('data\n ', data) })

6.1.3 渲染進程給主進程發送同步消息
1. 引入ipcRender
<!--src/index.html--> <button id="sendSync">渲染進程和主進程同步通信</button> <script src="render/ipcRender.js"></script>
2. 引入ipcMain
// 在主進程src/index.js中引入 const createWindow = () => { // 創建菜單 // 引入菜單模塊 require('./main/ipcMain.js') };
3. 渲染進程給主進程同步通信
// src/render/ipcMain.js let sendSync = document.querySelector('#sendSync'); // 渲染進程和主進程同步通信 sendSync.onclick = function () { // 同步廣播數據 let msg = ipcRenderer.sendSync('sendsync', {name:'poetries', age: 23}) // 同步返回主進程反饋的數據 console.log('msg\n ', msg) }
4. 主進程接收數據處理
// src/main/ipcMain.js // 渲染進程和主進程同步通信 接收同步廣播 ipcMain.on('sendsync', (event, data)=> { // console.log('data\n ', data) // console.log('event\n ', event) // 主進程給渲染進程廣播數據 event.returnValue ='渲染進程和主進程同步通信 接收同步廣播,來自主進程的反饋.'; })

6.1.4 渲染進程廣播通知主進程打開窗口
一般都是在渲染進程中執行廣播操作,去通知主進程完成任務
1. 引入openWindow
<!--src/index.html--> <button id="sendSync">渲染進程和主進程同步通信</button> <script src="render/openWindow.js"></script>
2. 引入ipcMain2
// 在主進程src/index.js中引入 const createWindow = () => { // 創建菜單 // 引入菜單模塊 require('./main/ipcMain2.js') };
3. 渲染進程通知主進程打開窗口
// src/render/openWindow.js /* eslint-disable */ let openWindow = document.querySelector('#openWindow'); var { ipcRenderer } = require('electron'); // 渲染進程和渲染進程直接的通信======== openWindow.onclick = function () { // 通過廣播的形式 通知主進程執行操作 ipcRenderer.send('openwindow', {name:'poetries', age: 23}) }
4. 主進程收到通知執行操作
// src/main/ipcMain2.js /* eslint-disable */ let { ipcMain,BrowserWindow } = require('electron') const path = require('path') let win; // 接收到廣播 ipcMain.on('openwindow', (e, data)=> { // 調用window打開新窗口 win = new BrowserWindow({ width: 400, height: 300, }); win.loadURL(path.join('file:',__dirname, '../news.html')); win.webContents.openDevTools() win.on('closed', () => { win = null; }); })

6.2 渲染進程與渲染進程之間的通信
也就是兩個窗口直接的通信
6.2.1 localstorage傳值
Electron
渲染進程通過localstorage
給另一個渲染進程傳值
1. 引入openWindow
<!--src/index.html--> <button id="sendSync">渲染進程和主進程同步通信</button> <script src="render/openWindow.js"></script>
2. 引入ipcMain2
// 在主進程src/index.js中引入 const createWindow = () => { // 創建菜單 // 引入菜單模塊 require('./main/ipcMain2.js') };
3. 渲染進程通知主進程打開窗口
// src/render/openWindow.js /* eslint-disable */ let openWindow = document.querySelector('#openWindow'); var { ipcRenderer } = require('electron'); // 渲染進程和渲染進程直接的通信======== openWindow.onclick = function () { // 通過廣播的形式 通知主進程執行操作 ipcRenderer.send('openwindow', {name:'poetries', age: 23}) // ======= localstorage傳值 ===== localStorage.setItem('username', 'poetries') }
4. 新建news頁面
<!--src/news.html--> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> news page </body> <script src="render/news.js"></script> </html> // src/render/news.js let username = localStorage.getItem('username') console.log(username)
5. 主進程收到通知執行操作
// src/main/ipcMain2.js /* eslint-disable */ let { ipcMain,BrowserWindow } = require('electron') const path = require('path') let win; // 接收到廣播 ipcMain.on('openwindow', (e, data)=> { // 調用window打開新窗口 win = new BrowserWindow({ width: 400, height: 300, }); win.loadURL(path.join('file:',__dirname, '../news.html')); win.webContents.openDevTools() win.on('closed', () => { win = null; }); })
6.2.2 BrowserWindow和webContents方式實現
通過
BrowserWindow
和webContents
模塊實現渲染進程和渲染進程的通信
webContents
是一個事件發出者.它負責渲染並控制網頁,也是BrowserWindow
對象的屬性
需要了解的幾個知識點
- 獲取當前窗口的
id
const winId = BrowserWindow.getFocusedWindow().id;
- 監聽當前窗口加載完成的事件
win.webContents.on('did-finish-load',(event) => {
})
- 同一窗口之間廣播數據
win.webContents.on('did-finish-load',(event) => { win.webContents.send('msg',winId,'我是 index.html 的數據'); })
- 通過
id
查找窗口
let win = BrowserWindow.fromId(winId);
下面是具體演示
1. 引入openWindow
<!--src/index.html--> <button id="sendSync">渲染進程和主進程同步通信</button> <script src="render/openWindow.js"></script>
2. 引入ipcMain2
// 在主進程src/index.js中引入 const createWindow = () => { // 創建菜單 // 引入菜單模塊 require('./main/ipcMain2.js') };
3. 渲染進程通知主進程打開窗口
// src/render/openWindow.js /* eslint-disable */ let openWindow = document.querySelector('#openWindow'); var { ipcRenderer } = require('electron'); // 渲染進程和渲染進程直接的通信======== openWindow.onclick = function () { // 通過廣播的形式 通知主進程執行操作 ipcRenderer.send('openwindow', {name:'poetries', age: 23}) }
4. 主進程收到通知執行操作
// src/main/ipcMain2.js let { ipcMain,BrowserWindow } = require('electron') const path = require('path') let win; // 接收到廣播 ipcMain.on('openwindow', (e, userInfo)=> { // 調用window打開新窗口 win = new BrowserWindow({ width: 400, height: 300, }); win.loadURL(path.join('file:',__dirname, '../news.html')); // 新開窗口調試模式 win.webContents.openDevTools() // 把渲染進程傳遞過來的數據再次傳遞給渲染進程news // 等待窗口加載完 win.webContents.on('did-finish-load', ()=>[ win.webContents.send('toNews', userInfo) ]) win.on('closed', () => { win = null; }); })
5. news接收主進程傳遞的數據
數據經過渲染進程->主進程->
news
渲染進程
<!--news頁面--> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> news page </body> <script src="render/news.js"></script> </html> // src/render/news.js var { ipcRenderer } = require('electron'); // let username = localStorage.getItem('username') // console.log(username) // 監聽主進程傳遞過來的數據 ipcRenderer.on('toNews',(e, userInfo)=>{ console.log(userInfo) })


那么,這里有一個問題,
news
進程接收到了廣播后如何給出反饋呢?

1. 在主進程中獲取窗口ID傳遞
// src/main/ipcMain2.js let { ipcMain,BrowserWindow } = require('electron') const path = require('path') let win; // 接收到廣播 ipcMain.on('openwindow', (e, userInfo)=> { // 獲取當前窗口ID 放在第一行保險 因為后面也打開了新窗口使得獲取的ID有問題 let winId = BrowserWindow.getFocusedWindow().id // 調用window打開新窗口 win = new BrowserWindow({ width: 400, height: 300, }); win.loadURL(path.join('file:',__dirname, '../news.html')); // 新開窗口調試模式 win.webContents.openDevTools() // 把渲染進程傳遞過來的數據再次傳遞給渲染進程news // 等待窗口加載完 win.webContents.on('did-finish-load', ()=>[ win.webContents.send('toNews', userInfo, winId) ]) win.on('closed', () => { win = null; }); })
2. 在news進程中廣播數據
// src/render/news.js var { ipcRenderer } = require('electron'); // 注意這里 在渲染進程中需要從remote中獲取BrowserWindow const BrowerWindow = require('electron').remote.BrowserWindow; // let username = localStorage.getItem('username') // console.log(username) // 監聽主進程傳遞過來的數據 ipcRenderer.on('toNews',(e, userInfo, winId)=>{ // windID 第一個窗口ID // 獲取對應ID的窗口 let firstWin = BrowerWindow.fromId(winId) firstWin.webContents.send('toIndex', '來自news進程反饋的信息') console.log(userInfo) })
3. 在另一個渲染進程中處理廣播
/* eslint-disable */ let openWindow = document.querySelector('#openWindow'); var { ipcRenderer } = require('electron'); // 渲染進程和渲染進程直接的通信======== openWindow.onclick = function () { // 傳遞消息給主進程 ipcRenderer.send('openwindow', {name:'poetries', age: 23}) // 傳遞給打開的窗口 渲染進程和渲染進程直接的通信 localStorage.setItem('username', 'poetries') } // 接收news渲染進程傳遞回來的消息 ipcRenderer.on('toIndex', (e, data)=>{ console.log('===', data) })

七、Electron Shell 模塊

7.1 Shell 模塊使用
Electron Shell
模塊在用戶默認瀏覽器 中打開URL
以及Electron DOM webview
標簽。Shell
既屬於主進程模塊又是渲染進程模塊
shell
模塊提供了集成其他桌面客戶端的關聯功能
1. 引入
<!--index.html--> <button id="shellDom">通過shell打開外部鏈接</button> <script src="render/shell.js"></script>
2. shell.js
// src/render/shell.js const { shell } = require('electron') let shellDom = document.querySelector('#shellDom'); shellDom.onclick = function (e) { shell.openExternal('https://github.com/poetries') }
7.2 Electron DOM
<webview>
標簽
Webview
與iframe
有點相似,但是與iframe
不同,webview
和你的應用運行的是不同的進程。它不擁有渲染進程的權限,並且應用和嵌入內容之間的交互全部都是異步的。因為這能 保證應用的安全性不受嵌入內容的影響。
<!--src/index.html中引入--> <webview id="webview" src="http://blog.poetries.top" style="position:fixed; width:100%; height:100%"> </webview>
7.3 shell
模塊<webview>
結合Menu
模塊使用案例
1. 新建src/render/webview.js
/* eslint-disable */ var { ipcRenderer } = require('electron'); let myWebview = document.querySelector('#myWebview') ipcRenderer.on('openwebview', (e, url)=>{ myWebview.src = url })
2. 引入src/index.html
<webview id="myWebview" src="http://blog.poetries.top" style="position:fixed; width:100%; height:100%"> </webview> <script src="render/webview.js"></script>
3. 新建src/main/menu.js
/* eslint-disable */ const { shell, Menu, BrowserWindow } = require('electron'); // 當前窗口渲染網頁 function openWebView(url) { // 獲取當前窗口Id let win = BrowserWindow.getFocusedWindow() // 廣播通知渲染進程打開webview win.webContents.send('openwebview', url) } // 在窗口外打開網頁 function openWeb(url) { shell.openExternal(url) } let template = [ { label: '幫助', submenu: [ { label: '關於我們', click: function () { openWeb('http://blog.poetries.top') } }, { type: 'separator' }, { label: '聯系我們', click: function () { openWeb('https://github.com/poetries') } } ] }, { label: '加載網頁', submenu: [ { label: '博客', click: function () { openWebView('http://blog.poetries.top') } }, { type: 'separator' // 分隔符 }, { label: 'GitHub', click: function () { openWebView('https://github.com/poetries') } }, { type: 'separator' // 分隔符 }, { label: '簡書', click: function () { openWebView('https://www.jianshu.com/users/94077fcddfc0/timeline') } } ] }, { label: '視頻網站', submenu: [ { label: '優酷', click: function () { openWebView('https://www.youku.com') } }, { type: 'separator' // 分隔符 }, { label: '愛奇藝', click: function () { openWebView('https://www.iqiyi.com/') } }, { type: 'separator' // 分隔符 }, { label: '騰訊視頻', click: function () { openWebView('https://v.qq.com/') } } ] } ] let m = Menu.buildFromTemplate(template) Menu.setApplicationMenu(m)
4. 引入menu
// 在主進程src/index.js中引入 const createWindow = () => { // 創建菜單 // 引入菜單模塊 require('./main/menu.js') };

八、Electron dialog 彈出框

dialog
屬於主進程中的模塊
dialog
模塊提供了api
來展示原生的系統對話框,例如打開文件框,alert
框, 所以web
應用可以給用戶帶來跟系統應用相同的體驗
1. 在src/index.html中引入
<button id="showError">showError</button><br /> <button id="showMsg">showMsg</button><br /> <button id="showOpenDialog">showOpenDialog</button><br /> <button id="saveDialog">saveDialog</button><br /> <script src="render/dialog.js"></script>
2. 新建render/dialog.js
// render/dialog.js let showError = document.querySelector('#showError'); let showMsg = document.querySelector('#showMsg'); let showOpenDialog = document.querySelector('#showOpenDialog'); let saveDialog = document.querySelector('#saveDialog'); var {remote} = require('electron') showError.onclick = function () { remote.dialog.showErrorBox('警告', '操作有誤') } showMsg.onclick = function () { remote.dialog.showMessageBox({ type: 'info', title: '提示信息', message: '內容', buttons: ['確定', '取消'] },function(index){ console.log(index) }) } showOpenDialog.onclick = function () { remote.dialog.showOpenDialog({ // 打開文件夾 properties: ['openDirectory', 'openFile'] // 打開文件 // properties: ['openFile'] }, function (data) { console.log(data) }) } saveDialog.onclick = function () { remote.dialog.showSaveDialog({ title: 'Save File', defaultPath: '/Users/poetry/Downloads/', // filters 指定一個文件類型數組,用於規定用戶可見或可選的特定類型范圍 filters: [ { name: 'Images', extensions: ['jpg', 'png', 'gif'] }, { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] }, { name: 'Custom File Type', extensions: ['as'] }, { name: 'All Files', extensions: ['*'] } ] }, function (path) { // 不是真的保存 ,具體還需nodejs處理 console.log(path) }) }
showError
remote.dialog.showErrorBox('警告', '操作有誤')

showMessageBox
remote.dialog.showMessageBox({ type: 'info', title: '提示信息', message: '內容', buttons: ['確定', '取消'] },function(index){ console.log(index) })

showOpenDialog
remote.dialog.showOpenDialog({ // 打開文件夾 properties: ['openDirectory', 'openFile'] // 打開文件 // properties: ['openFile'] }, function (data) { console.log(data) })

showSaveDialog
remote.dialog.showSaveDialog({ title: 'Save File', defaultPath: '/Users/poetry/Downloads/', // filters 指定一個文件類型數組,用於規定用戶可見或可選的特定類型范圍 filters: [ { name: 'Images', extensions: ['jpg', 'png', 'gif'] }, { name: 'Movies', extensions: ['mkv', 'avi', 'mp4'] }, { name: 'Custom File Type', extensions: ['as'] }, { name: 'All Files', extensions: ['*'] } ] }, function (path) { // 不是真的保存 ,具體還需nodejs處理 console.log(path) })

九、實現一個類似EditPlus的簡易記事本代碼編輯器
代碼 https://github.com/poetries/electron-demo/tree/master/notepad
十、系統托盤、托盤右鍵菜單、托盤圖標閃爍

系統托盤,托盤右鍵菜單、托盤圖標閃爍 點擊右上角關閉按鈕隱藏到托盤(仿殺毒軟件)
1. 引入文件
// src/index.js const createWindow = () => { require('./main/tray.js') };
2. Electron 創建任務欄圖標以及任務欄圖標右鍵菜單
// src/main/tray.js var { Menu, Tray, app, BrowserWindow } = require('electron'); const path = require('path'); var appIcon = new Tray(path.join(__dirname, '../static/lover.png')); const menu = Menu.buildFromTemplate([ { label: '設置', click: function() {} //打開相應頁面 }, { label: '幫助', click: function() {} }, { label: '關於', click: function() {} }, { label: '退出', click: function() { // BrowserWindow.getFocusedWindow().webContents().send('close-main-window'); app.quit(); } }]) // 鼠標放上去提示信息 appIcon.setToolTip('hello poetries'); appIcon.setContextMenu(menu);

3. 監聽任務欄圖標的單擊、雙擊事件
// 實現點擊關閉按鈕,讓應用保存在托盤里面,雙擊托盤打開 let win = BrowserWindow.getFocusedWindow() win.on('close', (e)=>{ e.preventDefault() win.hide() }) iconTray.on('double-click', (e)=>{ win.show() })
4. Electron 點擊右上角關閉按鈕隱藏任務欄圖標
const win = BrowserWindow.getFocusedWindow(); win.on('close', (e) =>{ console.log(win.isFocused()); if (!win.isFocused()) { win = null; } else { e.preventDefault();/*阻止應用退出*/ win.hide();/*隱藏當前窗口*/ } })
5. Electron 實現任務欄閃爍圖標
var appIcon = new Tray(path.join(__dirname, '../static/lover.png')); timer = setInterval(function() { count++; if (count % 2 == 0) { appIcon.setImage(path.join(__dirname, '../static/empty.ico')) } else { appIcon.setImage(path.join(__dirname, '../static/lover.png')) } }, 500);
十一、消息通知、監聽網絡變 化、網絡變化彈出通知框
11.1 消息通知
1. Electron 實現消息通知
Electron
里面的消息通知是基於h5
的通知api
實現的
文檔 https://developer.mozilla.org/zh-CN/docs/Web/API/notification
1. 新建notification.js
// h5api實現通知 const path = require('path') let options = { title: 'electron 通知API', body: 'hello poetries', icon: path.join('../static/img/favicon2.ico') // 通知圖標 } document.querySelector('#showNotification').onclick = function () { let myNotification = new window.Notification(options.title, options) // 消息可點擊 myNotification.onclick = function () { console.log('click notification') } }
2. 引入
<!--src/index.html--> <button id="showNotification">彈出消息通知</button> <script src="render/notification.js"></script>
mac
上的消息通知

11.2 監聽網絡變化
1. 基本使用
// 監聽網絡變化 // 端開網絡 再次連接測試 window.addEventListener('online', function(){ console.log('online') }); window.addEventListener('offline', function(){ console.log('offline') });
2. 監聽網絡變化實現消息通知
// 端開網絡 再次連接測試 // 監聽網絡變化實現消息通知 window.addEventListener('online', function(){ console.log('online') }); window.addEventListener('offline', function(){ // 斷開網絡觸發事件 var options = { title: 'QQ郵箱', body: '網絡異常,請檢查你的網絡', icon: path.join('../static/img/favicon2.ico') // 通知圖標 } var myNotification = new window.Notification(options.title, options) myNotification.onclick = function () { console.log('click notification') } });

十二、注冊全局快捷鍵/剪切板事件/nativeImage 模塊
Electron
注冊全局快捷鍵 (globalShortcut
) 以及clipboard
剪 切板事件以及nativeImage
模塊(實現類似播放器點擊機器碼自動復制功 能)
12.1 注冊全局快捷鍵

1. 新建src/main/shortCut.js
const {globalShortcut, app} = require('electron') app.on('ready', ()=>{ // 注冊全局快捷鍵 globalShortcut.register('command+e', ()=>{ console.log(1) }) // 檢測快捷鍵是否注冊成功 true是注冊成功 let isRegister = globalShortcut.isRegistered('command+e') console.log(isRegister) }) // 退出的時候取消全局快捷鍵 app.on('will-quit', ()=>{ globalShortcut.unregister('command+e') })
2. 引入src/index.js
// 注意在外部引入即可 不用放到app中 require('./main/shortCut.js')
12.2 剪切板clipboard、nativeImage 模塊

1. html
<!--src/index.html--> <div> <h2>雙擊下面信息復制</h2> <p id='msg'>123456789</p> <button id="plat">粘貼</button><br /> <input id="text" type="text"/> </div>. <div> <h2>復制圖片到界面</h2> <button id="copyImg">復制圖片</button><br /> </div> <script src="render/clipboard.js"></script>
2. 新建src/render/clipboard.js
// clipboard可以在主進程或渲染進程使用 const { clipboard, nativeImage } = require('electron') //復制 // 運行ctrl+v可看到復制的內容 // clipboard.writeText('poetries') // clipboard.readText() //獲取復制的內容 粘貼 // 雙擊復制消息 let msg = document.querySelector('#msg') let plat = document.querySelector('#plat') let text = document.querySelector('#text') msg.ondblclick = function () { clipboard.writeText(msg.innerHTML) alert(msg.innerHTML) } plat.onclick = function () { text.value = clipboard.readText() } // 復制圖片顯示到界面 let copyImg = document.querySelector('#copyImg') copyImg.onclick = function () { // 結合nativeImage模塊 let image = nativeImage.createFromPath('../static/img/lover.png') // 復制圖片 clipboard.writeImage(image) // 粘貼圖片 let imgSrc = clipboard.readImage().toDataURL() // base64圖片 // 顯示到頁面上 let imgDom = new Image() imgDom.src = imgSrc document.body.appendChild(imgDom) }
十三、結合electron-vue
13.1 electron-vue 的使用
1. electron-vue 的一些資源
Electron-vue
文檔 https://simulatedgreg.gitbooks.io/electron-vue/content/cn
2. electron-vue 環境搭建、創建項目
npm install -g vue-cli vue init simulatedgreg/electron-vue my-project cd my-project yarn # or npm install yarn run dev # or npm run dev
3. electron-vue 目錄結構分析

13.2 electron-vue 中使用 sass/ElementUi
1. electron-vue UI 框架 ElementUi 的使用
2. electron-vue 中使用 sass
# 安裝 sass-loader: npm install --save-dev sass-loader node-sass <!--vue 文件中修改 style 為如下代碼:--> <style lang="scss"> body { /* SCSS */ } </style>
13.3 electron-vue 中隱藏頂部菜單隱藏
electron-vue 中隱藏頂部菜單隱藏頂部最大化、最小化、關閉按鈕 自定最大化、最小化 、關閉按鈕
1. electron-vue 中隱藏頂部菜單
// src/main/index.js mainWindow.setMenu(null)
2. electron-vue 中隱藏關閉 最大化 最小化按鈕
// src/main/index.js mainWindow = new BrowserWindow({ height: 620, useContentSize: true, width: 1280, frame: false /*去掉頂部導航 去掉關閉按鈕 最大化最小化按鈕*/ })
3 .electron-vue 自定義關閉/最大化最小化按鈕
// 注意在mac下不需要監聽窗口最大最小化、以為系統默認支持,這個只是針對windows平台 ipc.on('window-min',function() { mainWindow.minimize(); }) //登錄窗口最大化 ipc.on('window-max',function(){ if (mainWindow.isMaximized()) { mainWindow.restore(); } else { mainWindow.maximize(); } }) ipc.on('window-close',function() { mainWindow.close(); })
4. electron-vue 自定義導航可拖拽
- 可拖拽的
css
:-webkit-app-region: drag;
- 不可拖拽的
css
:-webkit-app-region: no-drag;
13.4 使用electron-vue開發輿情監控系統
13.4.1 配置開發環境
1. 項目搭建
npm install -g vue-cli vue init simulatedgreg/electron-vue my-project cd my-project yarn # or npm install yarn run dev # or npm run dev
2. 安裝一些依賴
# 安裝 sass-loader: npm install --save-dev sass-loader node-sass # 安裝elementUI、js-md5 npm i element-ui js-md5 -S 在.electron-vue/webpack.renderer.config.js中配置sass-loader就可以編寫``sass`了 <!--vue 文件中修改 style 為如下代碼:--> <style lang="scss"> body { /* SCSS */ } </style>
13.4.2 主進程配置
1. src/main/index.js
function createWindow () { // 去掉頂部菜單 mainWindow.setMenu(null) // 菜單項 require('./model/menu.js'); // 系統托盤相關 require('./model/tray.js');
2. src/main/menu.js
菜單配置
const { Menu,ipcMain,BrowserWindow} = require('electron'); //右鍵菜單 const contextMenuTemplate=[ { label: '復制', role: 'copy' }, { label: '黏貼', role: 'paste' }, { type: 'separator' }, //分隔線 { label: '其他功能', click: () => { console.log('click') } } ]; const contextMenu=Menu.buildFromTemplate(contextMenuTemplate); ipcMain.on('contextmenu',function(){ contextMenu.popup(BrowserWindow.getFocusedWindow()); })
3. src/main/tray.js
系統托盤配置
托盤點擊監聽事件只有在
windows
下才生效,mac
系統默認支持
(function () { const path=require('path'); const {app,Menu,BrowserWindow,Tray, shell} = require('electron'); //創建系統托盤 const tray = new Tray(path.resolve(__static, 'favicon.png')) //給托盤增加右鍵菜單 const template= [ { label: '設置', click: function () { shell.openExternal('http://blog.poetries.top') } }, { label: '幫助', click: function () { shell.openExternal('http://blog.poetries.top/2019/01/06/electron-summary') } }, { label: '關於', click: function () { shell.openExternal('https://github.com/poetries/yuqing-monitor-electron') } }, { label: '退出', click: function () { // BrowserWindow.getFocusedWindow().webContents().send('close-main-window'); app.quit(); } } ]; const menu = Menu.buildFromTemplate(template); tray.setContextMenu(menu); tray.setToolTip('輿情監控系統'); //監聽關閉事件隱藏到系統托盤 // 這里需要注意:在window中才生效,mac下系統默認支持 // var win = BrowserWindow.getFocusedWindow(); // win.on('close',(e)=>{ // if(!win.isFocused()){ // win=null; // }else{ // e.preventDefault(); /*阻止應用退出*/ // win.hide(); /*隱藏當前窗口*/ // } // }) // //監聽托盤的雙擊事件 // tray.on('double-click',()=>{ // win.show(); // }) })()
4. src/main/shortCut.js
快捷鍵配置
在src/main/index.js
中引入(require('src/main/shortCut.js')
)即可,不需要放到app
監控中
var {globalShortcut, app} = require('electron') app.on('ready', ()=>{ // 注冊全局快捷鍵 globalShortcut.register('command+e', ()=>{ console.log(1) }) // 檢測快捷鍵是否注冊成功 true是注冊成功 let isRegister = globalShortcut.isRegistered('command+e') console.log(isRegister) }) // 退出的時候取消全局快捷鍵 app.on('will-quit', ()=>{ globalShortcut.unregister('command+e') })
13.4.3 渲染進程配置
1. src/render/main.js配置
import Vue from 'vue' import axios from 'axios' import App from './App' import router from './router' import store from './store' import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; import VueHighcharts from 'vue-highcharts'; import VueSocketIO from 'vue-socket.io' Vue.use(ElementUI); Vue.use(VueHighcharts); //引入socket.io配置連接 Vue.use(new VueSocketIO({ debug: true, connection: 'http://118.123.14.36:3000', vuex: { store, actionPrefix: 'SOCKET_', mutationPrefix: 'SOCKET_' } })) if (!process.env.IS_WEB) Vue.use(require('vue-electron')) Vue.http = Vue.prototype.$http = axios Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ components: { App }, router, store, template: '<App/>' }).$mount('#app')
2. 路由配置src/renderer/router/index.js
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new Router({ routes: [ { path: '/home', name: 'home', component: require('@/components/Home').default }, { path: '/report', name: 'report', component: require('@/components/Report').default }, { path: '/negativereport', name: 'negativereport', component: require('@/components/NegativeReport').default }, { path: '/positivereport', name: 'positivereport', component: require('@/components/PositiveReport').default }, { path: '/keyword', name: 'keyword', component: require('@/components/KeyWord').default }, { path: '/alarm', name: 'alarm', component: require('@/components/Alarm').default }, { path: '/msg', name: 'msg', component: require('@/components/Msg').default }, { path: '*', redirect: '/home' } ] })
3. 在渲染進程中使用主進程方式
// electron掛載到了vue實例上 $electron this.$electron.shell
13.4.4 多平台打包
需要注意的是打包
mac
版本在mac
系統上打包,打包window
則在windows
上打包,可以避免很多問題
# 在不同平台上執行即可打包應用
npm run build
13.4.4.1 打包介紹
1. electron 中構建應用最常用的模塊
electron-packager
electron-builder
electron-packager
和electron-builder
在自己單獨創建的應用用也可以完成打包功 能。但是由於配置太復雜所以我們不建議單獨配置
2. electron-forge
electron-forge package electron-forge make
3. electron-vue中的打包方式
# https://simulatedgreg.gitbooks.io/electron-vue/content/cn/using-electron-packager. html
# 之需要執行一條命令
npm run build
13.4.4.2 修改應用信息
1. 修改package.json

2. 修改src/index.ejs標題信息
3. 修改build/icons圖標
13.4.4.3 打包遇到的問題
1. 創建應用托盤的時候可能會遇到錯誤
- 把托盤圖片放在根目錄
static
里面,然后注意下面寫法。
var tray = new Tray(path.join(__static,'favicon.ico'))
- 如果托盤路徑沒有問題,還是包托盤相關錯誤的話,把托盤對應的圖片換成
.png
格式重試
2. 模塊問題可能會遇到的錯誤


解決辦法
- 刪掉
node_modules
然后重新用npm install
安裝依賴 - 用
yarn
來安裝模塊 - 用手機創建一個熱點電腦連上熱點重試
最后執行
yarn run build
即可

項目截圖
輿情監控系統頁面






系統系統托盤、
electron
消息通知 (類似騰訊新聞)


十四、更多參考
作者:poetries
鏈接:https://www.jianshu.com/p/2244653515a7
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
喜歡這篇文章?歡迎打賞~~