一,Electron 快速入门

1、简介

        Electron是利用web前端技术进行桌面应用开发的一套框架,它是由Github开发的,利用HTML、CSS、JavaScript来构建跨平台桌面应用程序的一个开源库。Electron通过将Chromium和Node.js合并到同一个运行时环境中,并将其打包成Mac、Windowns、Linux系统下的应用来实现这一目的。

        若想开发一个兼容多平台的桌面应用,以往常用的技术框架有GTK、QT等,这些框架受语言限制,且学习成本较高,效率有限。目前一些基于前端技术的hybrid框架很流行,且已经在多个领域得到了广泛的应用和验证,比如利用前端技术+相应的打包工具可开发适配多平台的应用(PC、微信公众号、小程序、Android、IOS等)。Electron就是这样一款框架,为前端技术人员利用web前端技术开发桌面应用带来了可能,开发人员可利用已经掌握的前端技术如Html、CSS、JavaScript,以及结合一些前端技术框架:Vue、Angular、React、webpack,加之浏览器渲染引擎、Electron封装的系统API快速实现一款桌面应用的开发,Electron做了大部分复杂的工作,开发人员只需要专注在核心业务和前端技术本身。同时,通过一定的优化,Electron可以做到很好的体验。

        目前有不少知名桌面应用采用Electron开发,如:开发人员熟知的Visual Studio Code、MongoDB桌面版管理工具、Skype桌面版、WhatsApp桌面版、HTTP网络测试工具Postman等。

2、组成

image

Chromium

        Chromium为Electron提供了强大的UI能力,可以不考虑兼容性的情况下,利用强大的Web生态来开发界面。本质上就是chromium(chrome开源版本)浏览器,有最新的东西都会在chromium测试,所以electron可以体验最新的api,这也是好处之一。其多进程架构图如下:

image.png

简单描述其原理:

        主进程中的RenderProcessHost和 render 进程中的RenderProcess是用来处理进程间通信的(IPC(Inter-Process Communication,进程间通信)。

        Render 进程中的 RenderView 内容基于 WebKit(浏览器引擎) 排版展示出来的

        Render 进程中的ResourceDispatcher是用来处理资源请求的。Render 进程中如果有请求则创建一个请 求 ID,转发到 IPC,由 Browser 进程中处理后返回。

        Chromium 是多进程架构,包括一个主进程,多个渲染进程。

Node.js

        Node.js是让Electron有了底层的操作能力,比如文件的读写,甚至是集成C++等等操作,并可以使用大量开源的npm包来完成开发需求

Native API

        Native API让Electron有了跨平台和桌面端的原生能力,比如说它有统一的原生界面,窗口、托盘、消息通知这些。

3、涉及前端技术

        Electron是基于Chromium和Node.js实现的,所以开发人员所需要使用到的前端技术主要包括以下方面:

        (1)、Html、CSS、JavaScript、ES6

        (2)、前端开发工具Vue、Angular、React等的一种

        (3)、其他网络、缓存、通讯等前端技术

4、架构

        Electron 架构和 Chromium 架构类似,也是具有1个主进程和多个渲染进程。但是也有区别:

        (1)、在各个进行中暴露了 Native API ,提供了 Native 能力

        (2)、引入了 Node.js,所以可以使用 Node 的能力

        (3)、但是渲染进程使用node 需要配置,下文会有所提到

        可以简单的理解为Electron为web项目套上了Node.js环境的壳,使得我们可以调用Node.js的丰富的API。这样我们可以用JavaScript来写桌面应用,拓展很多我们在web端不能做的事情。

        Electron的结构示意图如下:electron核心可以分成2个部分即:主进程和渲染进程。

image.png

 主进程

        Electron 运行 package.json 的 main 脚本的进程被称为主进程 (只有一个)。

        主进程特点:
                主进程连接着操作系统和渲染进程,可以把她看做页面和计算机沟通的桥梁
                进程间通信、窗口管理
                全局通用服务
                一些只能或适合在主进程做的事情,如浏览器下载、全局快捷键处理、托盘、session
                维护一些必要的全局状态

渲染进程
        渲染进程就是我们所熟悉前端环境了。只是载体改变了,从浏览器变成了window。

        注:出于安全考虑,渲染进程是不能直接访问本地资源的),因此都需要在主进程完成。

        渲染进程特点:
                Electron 使用了 Chromium 来展示 web 页面,所以 Chromium 多进程架构也被使用到。
                每个web页面运行在自己的进程中。每个渲染进程是相互独立的且只关心自己网页。
                使用BrowserWindow类开启一个渲染进程并将这个实例运行在该进程中,当一个被销毁后,相应的渲染进程也会被终止。
                渲染进程不能调用原生资源,但同样包含Node.js环境,所以可以引入Node.js。

主进程与渲染进程的区别
        主进程使用 BrowserWindow 实例创建网页。
        每个 BrowserWindow 实例都在自己的渲染进程里运行着一个网页。当一个 BrowserWindow 实例被销毁后,相应的渲染进程也会被终止。
        主进程管理所有页面和与之对应的渲染进程。
        由于在网页里管理原生 GUI 资源是非常危险而且容易造成资源泄露,所以在网页面调用 GUI 相关的 APIs 是不被允许的。如果你想在网页里使用 GUI 操作,其对应的渲染进程必须与主进程进行通讯,请求主进程进行相关的 GUI 操作。

二,Electron 桌面环境搭建

1、安装Node.js

(1)、默认安装,一直next

(2)、验证node是否安装成功

打开cmd窗口,在命令窗口输入命令:node -v ,如果出现版本号,即表示安装成功

还可以查看npm的版本,在cmd命令窗口中输入命令:npm -v

2、安装Electron

(1)、安装cnpm,采用淘宝镜像安装electron

打开cmd窗口,在命令窗口输入命令下载安装:

npm install cnpm -g  --registry=https://registry.npm.taobao.org

 在cmd命令窗口中输入命令:cnpm -v 查看版本信息

 (2)、安装electron,全局安装

打开cmd窗口,在命令窗口输入命令下载安装:cnpm install electron -g

在cmd命令窗口中输入命令:electron -v 查看版本信息

 3、测试项目

(1)、使用WebStorm新建空项目

 (2)、在根目录下新建一个html文件,文件名称为helloworld.html

(3)、在根目录下新建一个 main.js 文件,并填写如下代码,这个就是 Electron 的主进程文件

// 1、引入electron模块
let electron = require('electron');

// 2、创建electron引用
let app = electron.app;

// 3、创建窗口引用
let BrowserWindow = electron.BrowserWindow;

// 4、声明要打开的主窗口
let mainWindow = null;

app.on('ready',() => {

    // 设置窗口大小
    mainWindow = new BrowserWindow({width:300, height:200});

    // 加载哪个页面
    mainWindow.loadFile('helloworld.html');

    // 监听关闭事件,把主窗口设置为null,否则内存占用越来越多
    mainWindow.on('closed',() => {
        mainWindow = null;
    })
})

 (4)、在终端位置直接使用命令:npm init --yes 来初始化 package.json 文件

文件生成后如下:

{
  "name": "electron",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

(5)、运行项目,在终端位置输入命令:electron . (注意命令最后带 .)

三,Electron 项目实战

1、Electron remote模块的使用

        Electron 的 API 方法和模块也是分为可以在主进程和渲染进程中使用。那如果我们想在渲染进程中使用主进程中的模块方法时,可以使用 Electro remote 来解决在渲染和主进程间的通讯。这节我们就实现一个通过Web中的按钮打开新窗口。

(1)、在项目根目录下,新建一个 demo1.html 文件,然后快速生成html的基本结构,编写一个按钮,引入渲染的js页面。代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>demo1</title>
</head>
<body>
    <button id="btn">打开新的窗口</button>
    <script src="render/demo1.js"></script>
</body>
</html>

(2)、在render文件夹下,新建一个 demo3.js 文件,然后编写如下代码:

// 1、获取ID为btn的选择器
const btn = this.document.querySelector('#btn');

// 2、创建窗口引用
const BrowserWindow = require('electron').remote.BrowserWindow;

// 3、打开窗口
window.onload = function (){
    btn.onclick = () => {
        newWin = new BrowserWindow({width: 300, height: 200})
        
        newWin.loadFile('test1.html')
        
        newWin.on('close', () => {newWin = null})
    }
}

(3)、在项目根目录下创建test1页面,然后写入下面的代码:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test1</title>
</head>
<body>
    <h1>hello World!</h1>
</body>
</html>

(4)、 修改 main.js ,增加全量使用node.js,内容:

// 1、引入electron模块
let electron = require('electron');

// 2、创建electron引用
let app = electron.app;

// 3、创建窗口引用
let BrowserWindow = electron.BrowserWindow;

// 4、声明要打开的主窗口
let mainWindow = null;

app.on('ready',() => {

    // 设置窗口大小
    mainWindow = new BrowserWindow({
        width:300,
        height:200,
        // 增加全量使用node.js,因为我们要使用node里的fs模块
        // enableRemoteModule 设置为true,否则不允许在渲染器上使用 remote
        webPreferences: { nodeIntegration: true, enableRemoteModule: true}
    });

    // 加载哪个页面
    mainWindow.loadFile('demo1.html');

    // 监听关闭事件,把主窗口设置为null,否则内存占用越来越多
    mainWindow.on('closed',() => {
        mainWindow = null;
    })
})

 (5)、在终端中运行 electron . 命令,查看测试效果

2、Electron创建菜单和基本使用

(1)、在项目的根目录下新建一个main文件夹,用于存放主进程中用到的代码,在文件夹中新建文件menu.js,内容如下:

const { Menu, BrowserWindow } = require('electron')

// 菜单模板
let template = [
    {
        label: '前端技能',
        submenu: [
            {
                label: 'JavaScript',
                // 加入click事件
                click: () => {
                    win = new BrowserWindow({
                        width: 200,
                        height: 200,
                        webPreferences: { nodeIntegration: true }
                    })

                    win.loadFile('test2.html')

                    win.on('closed', () => {
                        win = null
                    })
                }
            },
            { label: 'TypeScript' }
        ]
    },
    {
        label: '后端技能',
        submenu: [
            { label: 'golang' },
            { label: 'Python' }
        ]
    }
]

// 设置菜单
let m = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(m)

 (2)、项目根目录新建测试文件 test2.html,内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
<h1>hello World!</h1>
</body>
</html>

(3)、项目根目录新建测试文件 demo2.html,内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo2</title>
</head>
<body>

</body>
</html>

 (4)、修改 main.js ,并在ready生命周期中,引入自定义菜单文件 require('./main/menu.js');

// 1、引入electron模块
let electron = require('electron');

// 2、创建electron引用
let app = electron.app;

// 3、创建窗口引用
let BrowserWindow = electron.BrowserWindow;

// 4、声明要打开的主窗口
let mainWindow = null;

app.on('ready',() => {
    // 自定义菜单
    require('./main/menu.js');

    // 设置窗口大小
    mainWindow = new BrowserWindow({
        width:300,
        height:200,
        // 增加全量使用node.js,因为我们要使用node里的fs模块
        // enableRemoteModule 设置为true,否则不允许在渲染器上使用 remote
        webPreferences: { nodeIntegration: true, enableRemoteModule: true}
    });

    // 加载哪个页面
    mainWindow.loadFile('demo2.html');

    // 监听关闭事件,把主窗口设置为null,否则内存占用越来越多
    mainWindow.on('closed',() => {
        mainWindow = null;
    })
})

 (5)、在终端中运行 electron . 命令,查看测试效果

 3、Electron制作右键菜单

(1)、菜单快捷键绑定

        接着上面的 demo2 ,绑定快捷键的属性是 accelerator 属性,比如我们要用快捷键新打开一个窗口,我们的 menu.js 代码可以写成这样:accelerator:`ctrl+n`

const { Menu, BrowserWindow } = require('electron')

// 菜单模板
let template = [
    {
        label: '前端技能',
        submenu: [
            {
                label: 'JavaScript',
                // 绑定快捷键
                accelerator:`ctrl+n`,
                // 加入click事件
                click: () => {
                    win = new BrowserWindow({
                        width: 200,
                        height: 200,
                        webPreferences: { nodeIntegration: true }
                    })

                    win.loadFile('test2.html')

                    win.on('closed', () => {
                        win = null
                    })
                }
            },
            { label: 'TypeScript' }
        ]
    },
    {
        label: '后端技能',
        submenu: [
            { label: 'golang' },
            { label: 'Python' }
        ]
    }
]

// 设置菜单
let m = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(m)

 (2)、创建右键菜单

        右键菜单的响应事件是写在渲染进程中的,也就是写在根目录下的demo2.html中的,所以要使用,就要用到remote模块进行操作了,先来看看右键的相应事件,我们打开 render 文件夹,然后打开 demo2.js 文件,编写一个右键菜单的监听事件,代码如下:

window.addEventListener('contextmenu',function () {
    alert('您好!');
})

         修改demo2.html,增加引入 <script src="render/demo2.js"></script>

当我们要使用 Menu 模块,它是主线中的模块,如果想在渲染线程中使用,就必须使用 remote 。demo2.js 代码如下: 

// window.addEventListener('contextmenu',function () {
//     alert('您好!');
// })

const { remote } = require('electron')

let rigthTemplate = [
    {
        label: '粘贴',
        accelerator: 'ctrl+v'
    },
    {
        label: '复制',
        accelerator: 'ctrl+c'
    }
]

let m = remote.Menu.buildFromTemplate(rigthTemplate);

window.addEventListener('contextmenu',function (e) {
    // 阻止当前窗口默认事件
    e.preventDefault();

    // 把菜单模板添加到右键菜单
    m.popup({window:remote.getCurrentWindow()});
})

(3)、程序打开调试模式,运行测试

        由于我们已经定义了顶部菜单,没有了打开调试模式的菜单了,这时候可以使用程序来进行打开。

        在 main.js 主进程的 ready 生命周期中加入如下这句代码就可以了:

        mainWindow.webContents.openDevTools()

4、Electron中通过链接打开浏览器

5、Electron中嵌入网页和打开子窗口

6、Electron Window.open子窗口和父窗口间的通信

7、Electron选择文件对话框

8、Electron保存对话框的操作

9、Electron消息对话框的操作

10、Electron断网提醒功能制作

11、Electron底部通知消息的制作

12、Electron注册全局快捷键

13、Electron剪贴板事件的使用

14、Electron-vue开发项目

四,Electron 项目进阶

1、克隆示例项目

(1)、项目仓库地址 

        git clone https://github.com/electron/electron-quick-start

(2)、下载完成后进入 electron-quick-start 目录中

 (3)、使用淘宝镜像下载项目所需的依赖

       采用如下命令设置淘宝镜像

                npm  config set ELECTRON_MIRROR https://npm.taobao.org/mirrors/electron/

         采用命令 npm install 进行所需依赖安装

 (4)、启动工程测试

        采用命令 npm start 启动项目


2、app常用事件

 (1)、ready 当electron完成初始化时被触发

app.on('ready',() => {
  console.log("*******ready******");
  createWindow();
})

 (2)、window-all-closed 所有窗口被关闭

app.on('window-all-closed', function () {
  console.log("*******window-all-closed******");
})

 (3)、quit 在所有应用程序退出时发出

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') {
    console.log("*******quit******");
    app.quit()
  }
})

(4)、open-file 在应用中要打开一个文件时发出

(5)、open-url 应用中要打开一个URL网址时发出

(6)、activate 当应用被激活时发出

3、webContents常用事件

 (1)、did-finish-load 导航完成时触发,即选项卡的旋转器将停止旋转,并指派 onload 事件后

mainWindow.webContents.on('did-finish-load',()=>{
  console.log("did-finish-load")
})

 (2)、dom-ready 一个框架中的文本加载完成后触发该事件

mainWindow.webContents.on('dom-ready',()=>{
  console.log("dom-ready")
})

4、process 进程对象

 (1)、编辑Index.html文件,如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'">
    <link href="./styles.css" rel="stylesheet">
    <title>Process</title>
  </head>
  <body>
    <button id="buttonProcess" >查看Process信息</button>
    <script src="./renderer.js"></script>
  </body>
</html>

 (2)、编辑渲染进程renderer.js,定义查看进程信息

var processBtn=document.getElementById('buttonProcess');

processBtn.onclick=getProcessInfo;

function getProcessInfo() {
    console.log("getCPUUsage=",process.getCPUUsage());
    console.log("arch=",process.arch);
    console.log("platform=",process.platform);
    console.log("env=",process.env);
}

5、File对象

        在文件系统中,使用HTML5 File 原生API操作文件

        DOM文件接口为原生文件提供了抽象,以便让用户使用HTML5文件API直接处理原生文件对象。Electron已经向文件对象接口添加了一个path属性,在文件系统中暴露出文件的真实路径

 (1)、在index.html文件中添加文件拖动代码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'">
    <link href="./styles.css" rel="stylesheet">
    <title>Process</title>
  </head>
  <body>
    <div class="for_file_drag" id="drag_test">
      <h2>File对象</h2>
      <span>往这里拖动文件</span>
    </div>
    <button id="buttonProcess" >查看Process信息</button>
    <script src="./renderer.js"></script>
  </body>
</html>

 (2)、在styles.css样式中新增拖拽区域的样式

.for_file_drag {
    width: 100%;
    height: 200px;
    background: pink;
}

 (4)、在renderer.js中添加如下方法

const electron = require("electron");
const fs = require('fs');

const dragWrapper = document.getElementById("drag_test");

dragWrapper.addEventListener('drop',(e) => {
    e.preventDefault();

    const files = e.dataTransfer.files;

    if(files && files.length>0){
        // 获取文件路径
        const path = files[0].path;
        console.log('path',path);

        // 获取文件内容
        const content = fs.readFileSync(path);
        console.log(content.toString());
    }
})

dragWrapper.addEventListener('dragover',(e)=>{
    e.preventDefault();
})

 (5)、测试

 

6、webview

在一个独立的frame和进程里面显示外部web内容

7、window.opn

8、BrowserWindowProxy

9、BrowserWindow

10、BrowserView

11、dialog

12、系统快捷键

13、主进程与渲染进程通信

14、原生应用菜单

15、网络

16、集成VUE

16、E

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐