上一篇文章,大概講述咱們即將要作的彈幕小工具是什麼樣的,將使用什麼樣的技術。那麼,從這一篇開始,咱們將一步步把想法落地成代碼。本文,咱們將使用Electron實現接收端,讓咱們的彈幕飛起來。javascript
如上圖所示,把放映PPT的同時,用戶能夠經過掃描小程序二維碼,實時發表本身的想法,達到互動的效果。html
若是你已經對Electron有了必定的瞭解,可跳過這部分的內容。java
Electron是由Github開發,用HTML,CSS和JavaScript來構建跨平臺桌面應用程序的一個開源庫。 Electron經過將Chromium和Node.js合併到同一個運行時環境中,並將其打包爲Mac,Windows和Linux系統下的應用來實現這一目的。node
在Electron中,主進程和渲染進程是一個很重要的概念。git
一個進程是計算機程序執行中的一個實例。 Electron 應用同時使用了 main(主進程) 和一個或者多個 rendere(渲染進程) 來運行多個程序。github
在 Node.js 和 Electron 裏面,每一個運行的進程包含一個 process對象。 這個對象做爲一個全局的提供當前進程的相關信息和操做方法。 做爲一個全局變量,它在應用內可以不用 require() 來隨時取到。web
主進程,一般是名爲main.js 的文件,是每一個 Electron 應用的入口文件。它控制着整個 App 的生命週期,從打開到關閉。 它也管理着系統原生元素好比菜單,菜單欄,Dock 欄,托盤等。 主進程負責建立 APP 的每一個渲染進程。並且整個 Node API 都集成在裏面。npm
每一個 app 的主進程文件都定義在 package.json 中的 main 屬性當中。這也是爲何 electron. 可以知道應該使用哪一個文件來啓動。json
在Chromium中, 這個進程被稱爲 "瀏覽器進程"。它在Electron被從新命名, 以免與渲染器進程混淆。小程序
渲染進程是你的應用內的一個瀏覽器窗口。與主進程不一樣的是,它可以同時存在多個並且運行在不同的進程。並且它們也可以被隱藏。
在一般的瀏覽器內,網頁一般運行在一個沙盒的環境擋住而且不可以使用原生的資源。 然而 Electron 的用戶在 Node.js 的 API 支持下能夠在頁面中和操做系統進行一些低級別的交互。
從上面的效果圖能夠看出,界面主要由彈幕界面和小程序二維碼組成。
結合上面說的主進程和渲染進程,咱們能夠將彈幕和二維碼分別放在兩個渲染進程中,主進程和nodejs服務端進行websocket通訊,並將接收到的數據分發到對應的渲染進程。
官方有個快速開始的示例,咱們能夠經過這個示例來大概瞭解一下整個應用是怎麼跑的。
# 克隆示例項目的倉庫
$ git clone https://github.com/electron/electron-quick-start
# 進入這個倉庫
$ cd electron-quick-start
# 安裝依賴並運行
$ npm install && npm start
複製代碼
咱們在package.json能夠看到,main屬性的值爲main.js,沒錯,那就是主進程的入口文件了。在這個入口文件中,經過BrowserWindow建立了一個瀏覽器窗口,並加載了一個html文件。是的,這就是所說的渲染進程。
這樣看來,咱們要作的事情其實仍是很簡單的,基本上和咱們寫靜態頁面同樣。建立瀏覽器窗口的代碼寫好以後,剩下的就是寫靜態頁面了。固然,還須要考慮數據通訊的方案。
在主進程中,經過API BrowserWindow 建立兩個渲染進程,分別展現彈幕主界面和二維碼。
function createMainWindow() {
mainWindow = new BrowserWindow({
width: 1920,
height: 1080,
transparent: true,
frame: false,
resizable: false,
alwaysOnTop: true,
center: true,
skipTaskbar: true,
autoHideMenuBar: true,
focusable: false
});
mainWindow.setAlwaysOnTop(true, 'pop-up-menu'); //必定要這樣設置 要否則在mac下全屏播放PPT的時候看不到
mainWindow.maximize();//窗口最大化
mainWindow.setIgnoreMouseEvents(true); //點擊穿透
mainWindow.loadURL(`file://${__dirname}/app/index.html`);
}
function createQrcodeWindow() {
qrcodeWindow = new BrowserWindow({
width: 200,
height: 200,
transparent: true,
frame: false,
resizable: false,
minimizable: false,
maximizable: false,
alwaysOnTop: true,
center: true,
skipTaskbar: true,
autoHideMenuBar: true
});
qrcodeWindow.setAlwaysOnTop(true, 'pop-up-menu'); //必定要這樣設置 要否則在mac下全屏播放PPT的時候看不到
qrcodeWindow.loadURL(`file://${__dirname}/app/qrcode.html`);
}
複製代碼
對於建立窗口的配置項,仍是比較多的,你們能夠大概通讀一遍官方文檔,知道到底能作些什麼。
因爲咱們的彈幕是一直在最上面的,並且不能影響下層的操做,因此咱們設置了透明、無邊框、且可忽略了鼠標事件(點擊可穿透),這樣在彈幕的同時,還能夠看到下層窗口並進行相關操做。此外,咱們還設置了禁止放大縮小,並且將彈幕窗口最大化。
因爲不但願窗口出如今任務欄裏,畢竟彈幕一直運行着,放在任務欄中,佔地方且礙眼,咱們更但願它出如今系統托盤中,以下圖:
上圖爲Mac效果,Windows會出如今右下角。
咱們能夠經過API Tray設置系統托盤。
function initTrayMenu() {
let iconPath = path.join(__dirname, 'ico/favicon.ico');
if (os.type() === "Darwin") {
iconPath = path.join(__dirname, 'ico/favicon.png');
}
const nimage = nativeImage.createFromPath(iconPath);
tray = new Tray(nimage);
tray.setToolTip('彈幕');
const contextMenu = Menu.buildFromTemplate([
{
label: '顯示彈幕',
type: 'radio',
click: showMainWindow
},
{
label: '關閉彈幕',
type: 'radio',
click: hideMainWindow
},
{
type: 'separator'
},
{
label: '顯示二維碼',
type: 'radio',
click: showQrcodeWindow
},
{
label: '隱藏二維碼',
type: 'radio',
click: hideQrcodeWindow
},
{
type: 'separator'
},
{
label: '退出',
type: 'normal',
click: function () {
app.quit();
}
}
]);
tray.setContextMenu(contextMenu); //設置菜單
tray.on('click', handleToggleShowMainWindow);
}
複製代碼
在咱們的設計中,接受端和服務端之間的通訊,採用websocket協議。因此,除了建立渲染進程以外,主進程還有一個很重要的任務,就是和服務端創建websocket鏈接。
咱們選擇使用ws模塊來快捷創建鏈接:
const ws = require('ws');
const Socket = new ws('wss://danmu.xxx.com');//參數爲socket服務地址
Socket.on('open', function open() {
//在初始化的時候,發送初始化消息,並帶上接收客戶端ID,獲取小程序二維碼
const initData = {
type: 'INIT',
clientId: 'xxxx' //客戶端惟一ID
}
Socket.send(JSON.stringify(initData));
});
Socket.on('message', function incoming(data) {
// 對收到的數據進行分發
try {
const received_msg = JSON.parse(data);
if (received_msg.type === 'qrcode') {
qrcodeWindow.webContents.send('qrcodeBase64', received_msg.data);
}else{
mainWindow.webContents.send('new-message', received_msg);
}
} catch (error) {
console.log(error);
}
});
複製代碼
在和服務器創建鏈接以後,咱們發送了初始化的消息,咱們約定,服務端收到初始化消息以後,會根據clientId生成帶參數的小程序二維碼,並將二維碼的base64數據返回來。因此,咱們接收到新消息的時候,對消息類型進行判斷並轉發到對應的渲染進程。
上面提到,須要提供一個惟一的客戶端ID傳給nodejs服務端,用於生成惟一的小程序二維碼。咱們可使用如下方法生成:
function generateUUID() {
return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// 你們不妨思考一下,爲何可使用這個方法生成UUID呢?
複製代碼
此外,咱們但願第一次使用這個彈幕應用以後,這個ID就一直不變,畢竟二維碼變來變去,對用戶是很不友好的。因此咱們須要將這個ID寫在本地文件中。偷懶,咱們也可使用別人寫好的模塊electron-store,原理就是把須要存儲在本地的信息寫在一個json文件中,各類特殊的路徑能夠經過app.getPath(name)來獲取。
如app.getPath("appData")能夠獲取當前用戶的應用數據文件夾,默認對應:
其餘更多的路徑可查看官方文檔。
這裏就要講到ipcMain、ipcRenderer以及webContents了,他們都是EventEmitter類的一個實例。
ipcMain: 當在主進程中使用時,它處理從渲染進程發送出來的異步和同步信息。 從渲染進程發送的消息將被髮送到該模塊。
ipcRenderer:你可使用它提供的一些方法從渲染進程發送同步或異步的消息到主進程。 也能夠接收主進程回覆的消息。
webContents:BrowserWindow對象的一個屬性,其send方法能夠向渲染進程發送異步消息,能夠發送任意參數。
看看二維碼的栗子:
//主進程中
ipcMain.on("qrcodeFinished", function () {
// doSomething
});
qrcodeWindow.webContents.send('qrcodeBase64', received_msg.data);
//渲染進程
const ipcRenderer = require('electron').ipcRenderer;
ipcRenderer.on('qrcodeBase64', function (event, data) {
const qrcode = document.getElementById('qrcode');
qrcode.src = data;
qrcode.style.display = 'block';
ipcRenderer.send("qrcodeFinished");
});
複製代碼
上面講到,咱們在主進程中建立了瀏覽器窗口,而後經過瀏覽器窗口實例去加載html文件,就將咱們的頁面展現了處理。因此,讓彈幕飛起來,就和咱們寫靜態頁面沒什麼區別了,數據的來源就是接收主進程發送過來的消息。
怎麼讓彈幕動起來呢,這個你們應該都不陌生,就是不斷更新彈幕的位置就能夠了。
這裏就不得不講到window.requestAnimationFrame()這一神器了。該方法告訴瀏覽器但願執行動畫並請求瀏覽器在下一次重繪以前調用指定的函數來更新動畫。該方法使用一個回調函數做爲參數,這個回調函數會在瀏覽器重繪以前調用。
具體的實如今這裏就不贅述了。
上面大概介紹了此應用開發的關鍵點,開發完成以後,就是打包階段了。
咱們使用electron-builder能夠很方便地進行應用打包。
安裝模塊:
npm install electron-builder --save-dev
複製代碼
在package.json中加入如下內容:
"scripts": {
"start": "electron .",
"pack:win": "electron-builder --win --ia32",
"pack:mac": "electron-builder --mac"
},
"build": {
"appId": "com.Barrage.app",
"productName": "彈幕666",
"copyright": "Copyright © 2018 ${author}",
"electronVersion": "3.0.4",
"mac": {
"icon": "ico/favicon.icns",
"artifactName": "${productName}_Setup_${version}.${ext}"
},
"win": {
"target": "nsis",
"icon": "ico/favicon.ico",
"artifactName": "${productName}_Setup_${version}.${ext}"
}
}
複製代碼
打包命令:
npm run pack:win
//or
npm run pack:mac
複製代碼
詳細使用能夠看看官網文檔。
本文大概介紹了開發Electron接收端的關鍵點。事實上,每個點展開講,花很大的篇幅也未必將全部細節講清楚。最好的方案,仍是應該本身動手去實踐一下,遇到問題解決問題,勢必有所收穫。
感謝耐心閱讀。以上,若有錯漏,歡迎指正!
剛纔在敲代碼的時候,屏幕上忽然飄來幾個字,真是讓人恐慌。後來排查了一下,應該是由於本文中放的截圖帶有個人客戶端二維碼,有朋友掃碼體驗了一波,因而我就收到了。
因而,趕忙補充一下搶先體驗版。功能還沒有完善,應該有很多bug。
@Author: TDGarden