编辑推荐: |
本文主要介绍了 Electron,包括其简介、工作机制、任务管理案例开发流程等。希望对您的学习有所帮助。
本文来自于稀土掘金,由火龙果软件Linda编辑、推荐。 |
|
文章主要介绍了 Electron,包括其简介、工作机制、任务管理案例开发流程等。Electron
用 JavaScript、HTML 和 CSS 构建跨平台桌面应用,区分主进程和渲染进程。文中通过案例讲解了初始化项目、调试、IPC
通信、窗口位置设定、关闭窗口等知识,最后提及下篇将探讨打包问题。
Electron简介
Electron是干什么的? 简单来讲,Electron 使用 JavaScript,HTML 和
CSS,来构建跨平台的桌面应用程序。
按照官方的说法:如果你可以建一个网站,你就可以建一个桌面应用程序。
和传统的桌面应用相比,使用Electron开发更容易上手,开发效率更高。并且,web技术应用广泛、生态繁荣,Electron可以使用几乎所有的Web生态领域及Node.js生态领域的组件和技术方案。
与网页应用相比,Electron基于Chromium 和 Node.js,可以避免令人头痛的浏览器兼容问题。而Web前端受限访问的文件系统、系统托盘、系统通知等,开发Electron应用时可以自由地使用。
简单理解Electron工作机制
使用Electron开发的桌面应用,类似于简易版的、定制版的Chrome浏览器,当然这个浏览器中的页面不能通过输入网址打开,而是由开发者写好的。
和浏览器架构类似,Electron应用程序区分主进程和渲染进程。
主进程负责控制应用程序的生命周期、创建和管理应用程序窗口,有着多种控制原生桌面功能的模块,例如菜单、对话框以及托盘图标。
渲染进程负责完成渲染界面、接收用户输入、响应用户的交互等工作。
一个Electron应用只有一个主进程,但可以有多个渲染进程。
在之前的文章中,我们有讲过浏览器中的进程,可略作参考。
案例入门
下面我们将从一个任务管理的案例入门,了解electron的整体开发流程和一些基本的细节知识。
案例效果
功能分析:
1、记录待完成任务和已完成任务
2、通过新建,添加待完成任务,并设置时间
3、点击完成任务,跳转到已完成界面;点击删除,可以删除任务
4、点击右上角的 × 按钮,可以关闭主界面,要再次打开主界面,可以通过系统托盘
5、设定的时间到了,会在右下角弹出提醒框,如下图所示。
初始化项目
项目是由原生js开发,在后面的文章中,我们会再探讨electron和vue、react这些前端框架的结合。
mkdir
tasky
cd tasky
npm init |
安装electron
创建一个hello world应用程序
(1)第一步,在项目根目录下,创建index.js,作为应用程序的入口文件。因为Electron是基于Node.js,所以入口文件使用Node.js语法。内容如下:
//引入两个模块:app 和 BrowserWindow
//app 模块,控制整个应用程序的事件生命周期。
//BrowserWindow 模块,它创建和管理程序的窗口。
const { app, BrowserWindow } = require('electron')
//在 Electron 中,只有在 app 模块的 ready 事件被激发后才能创建浏览器窗口
app.on('ready', () => {
//创建一个窗口
const mainWindow = new BrowserWindow()
//窗口加载html文件
mainWindow.loadFile('./src/main.html')
})
|
(2)第二步,创建窗口需要加载的html文件。
为了方便后面的文件管理,我们新建一个 src 文件夹,用于存放web页面资源,比如html、css、js、图片等。
//
./src/main.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible"
content="IE=edge">
  <meta name="viewport" content="width=device-width,
initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  hello world
</body>
</html>
|
(3)启动程序。
修改package.json的scripts,如下:
然后在项目根目录运行:npm run start。
这样我们的hello world应用程序就跑起来了。入门Electron,就是这么简单!
简单的基础调试
1、主进程运行时的一些提示信息会在命令行显示,比如,在index.js加入console.log
app.on('ready',
() => {
  console.log('just test console.log')
 const mainWindow = new BrowserWindow()
 mainWindow.loadFile('./src/main.html')
}) |
就可以在命令行看到打印的值:
当index.js中的内容发生改变,默认要手动重启,比较麻烦。这里我们加入nodemon,它可以监控node.js
源代码的变化,并自动重启应用。
安装: npm i nodemon --D
修改scripts:"start": "nodemon --watch
index.js --exec electron ."
再次运行npm run start,当index.js内容变化时,就会自动重新执行electron
.来重启应用。
2、窗口页面的调试方法和chrome浏览器类似。点击菜单栏的View --- Toggle Developer
Tools,或者按它对应的快捷键,就会出现我们熟悉的开发者工具界面。
当页面内容发生变化,可以点击View --- Reload,或者快捷键ctrl+r,刷新页面内容。页面热更新会后续讲到。
开始coding
项目目录结构如下,其中src文件夹存放的就是web页面相关的内容。
项目有两个窗口:主窗口和提醒窗口。在主窗口中管理任务,当任务设定时间到,会在屏幕右下角出现提醒窗口。
应用不涉及服务端数据,任务数据主要使用localStorage来存储。
创建主窗口:
const iconPath = path.join(__dirname, './src/img/icon.png')
//应用运行时的标题栏图标
let mainWindow
app.on('ready', () => {
  mainWindow = new BrowserWindow({
    resizable: false, //不允许用户改变窗口大小
    width: 800, //设置窗口宽高
    height: 600,
    icon: iconPath, //应用运行时的标题栏图标
    webPreferences:{
      backgroundThrottling: false, //设置应用在后台正常运行
      nodeIntegration:true, //设置能在页面使用nodejs的API
     contextIsolation: false,
     preload: path.join(__dirname, './preload.js')
    }
  })
mainWindow.loadURL(`file://${__dirname}/src/main.html`)
}
|
main.html的内容很简单,有兴趣的童鞋可以去看源码,这里就不贴了。
默认Electron应用顶部有标题栏和菜单栏。纵观各个桌面应用程序,基本都是定制的顶部控制区域。对于我们这个应用,暂时只要一个关闭按钮,所以我们将去掉这一部分,将窗口关闭按钮写在main.html页面中。
无边框窗口
要创建无边框窗口,只需在 BrowserWindow 的 options 中将 frame 设置为
false:
mainWindow
= new BrowserWindow({
  frame: false,
  ...
}) |
标题栏和菜单栏消失了,但也会有几个问题:
1、虽然菜单栏消失了,但是依然可以通过快捷键进行菜单操作,比如ctrl+shift+i打开开发者工具,为避免这种情况,我们需要去掉菜单栏:
2、默认情况下,无边框窗口是不可拖拽的。应用程序需要在 CSS 中指定 -webkit-app-region:
drag 来告诉 Electron 哪些区域是可拖拽的。
html,body {
  height: 100%;
  width: 100%;
}
body{
  -webkit-app-region: drag;
}
|
如果用上面的属性使整个窗口都可拖拽,则必须将其中的按钮标记为不可拖拽,否则按钮将无法点击。
.enable-click
{
  -webkit-app-region: no-drag;
} |
3、当点击自定义的窗口关闭按钮,我们并不希望退出程序,只是将窗口隐藏,可以通过系统托盘再次打开窗口。
系统托盘
程序启动时,将应用程序加入系统托盘。在Electron中,借助Tray模块实现。
const
{ app, BrowserWindow, Tray, Menu } = electron
const iconPath = path.join(__dirname, './src/img/icon.png')
let mainWindow, tray
app.on('ready', () => {
  mainWindow = new BrowserWindow({
   //... options
  })
 mainWindow.loadURL(`file://${__dirname}/src/main.html`)
 tray = new Tray(iconPath) //实例化一个tray对象,构造函数的唯一参数是需要在托盘中显示的图标url
 tray.setToolTip('Tasky') //鼠标移到托盘中应用程序的图标上时,显示的文本
  tray.on('click', () => { //点击图标的响应事件,这里是切换主窗口的显示和隐藏
    if(mainWindow.isVisible()){
      mainWindow.hide()
    }else{
      mainWindow.show()
    }
 })
  tray.on('right-click', () => { //右键点击图标时,出现的菜单,通过Menu.buildFromTemplate定制,这里只包含退出程序的选项。
    const menuConfig = Menu.buildFromTemplate([
    {
      label: 'Quit',
      click: () => app.quit()
    }
    ])
    tray.popUpContextMenu(menuConfig)
  })
})
|
IPC通信
回到上一个问题。点击页面内的按钮怎样隐藏窗口?这就需要用到IPC通信了。
IPC(Inter-Process Communication),就是进程间通信。Electron应用程序区分主进程和渲染进程,有时候,两者之间需要通信,传输一些数据、发送一些消息。
渲染进程 TO 主进程
比如,点击关闭按钮,就需要渲染进程向主进程发送隐藏主窗口的请求。
渲染进程使用Electron内置的ipcRenderer模块向主进程发送消息,ipcRenderer.send方法的第一个参数是消息管道名称。
//页面的js代码:
const electron = require('electron')
const { ipcRenderer } = electron
closeDom.addEventListener('click', () =>
{
  ipcRenderer.send('mainWindow:close')
})
|
主进程通过ipcMain接收消息,ipcMain.on方法的第一个参数也为消息管道的名称,与ipcRenderer.send的名称对应,第二个参数是接收到消息的回调函数。
//入口文件index.js
ipcMain.on('mainWindow:close', () => {
  mainWindow.hide()
})
|
主进程 TO 渲染进程
主进程向渲染进程发送消息是通过渲染进程的webContents。在mainWindow渲染进程设定了任务后,会传输给主进程任务信息,当任务时间到了,主进程会创建提醒窗口remindWindow,并通过remindWindow.webContents将任务名称发给remindWindow。
function createRemindWindow (task) {
  remindWindow = new BrowserWindow({
    //options
  })
  remindWindow.loadURL(`file://${__dirname}/src/remind.html`)
  //主进程发送消息给渲染进程
  remindWindow.webContents.send('setTask', task)
}
|
在remindWindow渲染进程中,通过ipcRenderer.on接受消息。
ipcRenderer.on('setTask', (event,task) =>
{
 document.querySelector('.reminder').innerHTML
=
   `<span>${decodeURIComponent(task)}</span>的时间到啦!`
})
|
渲染进程 TO 渲染进程
渲染进程之间传递消息,可以通过主进程中转,即窗口A先把消息发送给主进程,主进程再把这个消息发送给窗口B,这种非常常见。
也可以从窗口A直接发消息给窗口B,前提是窗口A知道窗口B的webContents的id。
ipcRenderer.sendTo(webContentsId, channel, ...args)
|
值得注意的是,我们在页面的js代码中使用了require,这也是Electron的一大特点,在渲染进程中可以访问Node.js
API。这样做的前提是在创建窗口时配置webPreferences的nodeIntegration:
true和contextIsolation: false:
mainWindow = new BrowserWindow({
  width: 800,
  height: 600,
  webPreferences:{
    nodeIntegration: true,
    contextIsolation: false
  }
})
|
窗口位置
当任务时间到,提醒窗口会在屏幕右下角出现。怎样设定窗口位置呢?
function
createRemindWindow (task) {
  //创建提醒窗口
  remindWindow = new BrowserWindow({
    //...options
  })
  //获取屏幕尺寸
  const size = screen.getPrimaryDisplay().workAreaSize
  //获取托盘位置的y坐标(windows在右下角,Mac在右上角)
  const { y } = tray.getBounds()
  //获取窗口的宽高
  const { height, width } = remindWindow.getBounds()
  //计算窗口的y坐标
  const yPosition = process.platform === 'darwin'
? y : y - height
  //setBounds设置窗口的位置
  remindWindow.setBounds({
  x: size.width - width, //x坐标为屏幕宽度 - 窗口宽度
  y: yPosition,
  height,
  width
})
  //当有多个应用时,提醒窗口始终处于最上层
  remindWindow.setAlwaysOnTop(true)
  remindWindow.loadURL(`file://${__dirname}/src/remind.html`)
} |
关闭窗口
提醒窗口会在一段时间后关闭,可以通过remindWindow.close()来关闭窗口。
当窗口关闭后,我们可以设置remindWindow = null来回收分配给该渲染进程的资源。
remindWindow.on('closed', () => { remindWindow = null })
|
结语
这篇文章主要是介绍electron的一些基础知识,下一篇文章,我们将探讨electron的打包问题,下次见。
凡能用JavaScript实现的,注定会被用JavaScript实现。
         --Jeff
Atwood
|
|