一、前言
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被重命名為Electron2016年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-packagerelectron-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
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
喜歡這篇文章?歡迎打賞~~

