基于Electron + nodejs + 小程序 实现弹幕小工具(上篇)

前言

上一篇文章,大概讲述咱们即将要作的弹幕小工具是什么样的,将使用什么样的技术。那么,从这一篇开始,咱们将一步步把想法落地成代码。本文,咱们将使用Electron实现接收端,让咱们的弹幕飞起来。javascript

效果图

如上图所示,把放映PPT的同时,用户能够经过扫描小程序二维码,实时发表本身的想法,达到互动的效果。html

Electron基本介绍

若是你已经对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);
}
复制代码

与nodejs服务端通讯

在咱们的设计中,接受端和服务端之间的通讯,采用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")能够获取当前用户的应用数据文件夹,默认对应:

  • Windows:%APPDATA%
  • Linux:$XDG_CONFIG_HOME or ~/.config
  • macOS: ~/Library/Application Support

其余更多的路径可查看官方文档。

主进程和渲染进程之间通讯

这里就要讲到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-updater给咱们的应用配置自动更新功能。在这里就不展开讲了。
  • 错误日志收集:一个完善的应用,应该要有错误日志的记录。
  • more...

总结

本文大概介绍了开发Electron接收端的关键点。事实上,每个点展开讲,花很大的篇幅也未必将全部细节讲清楚。最好的方案,仍是应该本身动手去实践一下,遇到问题解决问题,势必有所收获。

感谢耐心阅读。以上,若有错漏,欢迎指正!

后话

刚才在敲代码的时候,屏幕上忽然飘来几个字,真是让人恐慌。后来排查了一下,应该是由于本文中放的截图带有个人客户端二维码,有朋友扫码体验了一波,因而我就收到了。

因而,赶忙补充一下抢先体验版。功能还没有完善,应该有很多bug。

下载体验

@Author: TDGarden

相关文章
相关标签/搜索