Electron 已经不算新技术,最早是 github 从 Atom 编辑器衍生出来的框架。通过编写 Javascript, HTML, CSS 能快速编译出跨系统的桌面 app。Electron 的出现使得作为前端开发工程师的我们输出范围更广。

分享最近用 Electron 做的一个基于番茄工作法的小应用,由于实现难度不大,市面上已经有非常多类似的app。我们尝试用 Electron 来实现一个。

最终效果预览:

d5d29e8d2d09733ae79e032002535b2f.png

? 工作法

番茄工作法的核心是将任务颗粒拆分到单位时间内(25分钟)可以完成,在这25分钟内专注在这个任务三,不允许做任何与任务无关的事,在任务任务完成之后可以短暂休息一会,再继续工作。

所以这个 app 的重点是让你创建任务,⏳ 25分钟,帮让 focus on 当前在做的任务。

站在巨人的肩膀上开发

尝试新技术的时候,不要从零开始学习如何搭建技术栈,先做出来,遇到问题再查。 Electron 社区有很多优秀的沉淀,工具,模板,组件,教程等等。

搜索 react 关键字,找到了 electron-react-boilerplate 这个样板库, 这个库已经集成了 reactreduxsassflowhmr webpack 等工具,同时准备好 electron-builder 打包工具,作为 electron 新手,我们优先选择开箱即用的工具,快速开启业务开发。

SVG 和 React Component

大概画了一下草图,准备进入开发阶段。考虑后面会用到 svg icon,先在 FlatIcon 上找些免费的图标,下载 SVG 文件。

通过 SVGR 在线工具导入 svg 内容生成 React Component 代码。(svgr 也有 cli 等工具)

用 SVG Component 的好处是可以在代码上更灵活地控制样式,相比 png 图标可交互性强,复用率高。

d73a1eb19629204f2c6cb260097353c7.png

托盘和托盘弹窗

这个 app 启动的时候就隐藏在托盘菜单的一角,点击的时候显示 BrowserWindow,通过 Electron 提供的方法,可以获得托盘和托盘弹窗的 Bounds 信息,设置坐标位置。

const tray = new Tray(path.join(__dirname, '../static', 'tray.png'));

const mainWindow = new BrowserWindow({

frame: false,

resizable: true,

transparent: true

});

const showWindow = () => {

const { x, y } = getPositionFromActiveDisplay();

mainWindow.setPosition(x, y, true);

mainWindow.show();

};

const getPositionFromActiveDisplay = () => {

const trayBounds = tray.getBounds();

const windowBounds = mainWindow.getBounds();

const x = Math.round(trayBounds.x + trayBounds.width / 2 - windowBounds.width / 2);

const y = Math.round(trayBounds.y + trayBounds.height);

return { x, y };

};

59848d77f23eb23a4f86e329906eee80.png

?图的三角是由前端代码绘制的,加上 frame 和 electron 背景色,应该长这样。

6fff5cf27973f8c4ee313d8771fc7a3c.png

渲染线程和主线程

app 需要倒计时功能,告诉用户距离任务完成时间还有多久。Electron 有渲染进程和主线程,BrowserWindow 不可见的时候,渲染进程会尽量减少消耗,所以如果 Tick 在渲染进程的话,当 app 处于后台时会出现非常大的时间偏差。这里使用 Electron 提供的 ipcMain 和 ipcRenderer 做进程通信。

在主线程每秒发送 Tick 事件

// main.js

ipcMain.once('store-ready', event => {

const run = () => {

setTimeout(() => {

run();

event.sender.send('tick');

}, 1000);

};

run();

});

渲染进程就收事件并将 dispatch TICK action。

// app/index.js

const store = configureStore({

tasks: electronStore.getTasks()

});

ipcRenderer.send('store-ready');

ipcRenderer.on('tick', () => {

store.dispatch({

type: TICK

});

});

redux store 里面判断当前执行的任务计算倒计时时间。

switch (action.type) {

case TICK:

return {

...state,

rows: state.rows.map(task =>

task.id === state.currentId

? {

...task,

remain: Math.max(task.remain - 1, 0)

}

: task

)

};

数据持久存储

数据持久化有很多种方案,因为是前端浏览器,我们可以选择 localStorage, Cookie,indexDB 等等。考虑可靠性,持久化以及存储空间,还可以通过 Electron 写文件的方式,把数据写入到应用路径下。这样即使 app 被卸载了,只要数据没被清空,用户数据还在。

通过 Electron app getPath 可以获得应用存储路径

import { app } from 'electron';

app.getPath('userData');

mac 下应用 app 的路径是 /Users/user/Library/ApplicationSupport/focus。更简单的方式可以直接用开源库 electron-store,以 key-value 的格式存储 json 文件。

{

"tasks": {

"rows": [

{

"name": "任务名称",

"id": "91ac7f05-76f4-46ea-addb-f392a3a29b54",

"created_at": 1553398427806,

"plan": 1500,

"remain": 0,

"done": true

}

],

"currentId": "91ac7f05-76f4-46ea-addb-f392a3a29b54"

}

}

倒计时 UI

有些样式可能用 css 实现难度较大,而用 svg 的方式实现起来非常简单。比如倒计时 UI,路径圆角和路径长度用 CSS 实现复杂度较高。可以在 Sketch 上直接绘制处理,导出成 svg,直接通过 react 代码控制。

ba08337885cf87328e76c0a5a3fe0e96.png

export default function(props: Props) {

const offset = percentage * totalLength;

const cx =

Math.cos(percentage * Math.PI * 2 - Math.PI * 0.5) * radius + radius;

const cy =

Math.sin(percentage * Math.PI * 2 - Math.PI * 0.5) * radius + radius;

return (

...others

<circle

id="path-1"

cx={cx}

cy={cy}

r="32"

fill="white"

style={{ transition: '1s linear' }}

/>

<path

...others

strokeLinecap="round"

strokeDasharray={totalLength}

strokeDashoffset={offset}

style={{ transition: '1s linear' }}

/>

svg>

);

}

临界状态判断

app 在任务时间结束时需要有 Notification,由于?的 Tick 设计,判断任务是否完成可以放在 redux middleware 上。

// middlewares/tasks

export default ({ getState }) => next => action => {

if (typeof action === 'object' && action.type === 'TICK') {

const beforeCount = getTimeEndTaksCount(getState);

next(action);

const afterCount = getTimeEndTaksCount(getState);

if (beforeCount !== afterCount) {

new Notification('Focus,任务完成了吗?');

}

} else {

next(action);

}

};

经过一个 Tick action 之后,判断任务完成数是否有变化,并使用 HTML5 Notification 通知用户。

97cd62673ba298651060ccadaa710d95.png

Travis CI

功能开发完毕之后,使用 electron-builder 进行打包发布,构建之后推到 github release 下,用户可以直接在这下载到最新的包。

同样的, boilerplate 已经准备好 .travis.yml 文件,唯一需要我们操作的是在 https://github.com/settings/tokens/new 上生成 token,在 https://www.travis-ci.org/ 构建之前配置 Environment Variables, GH_TOKEN

e052181f4cc5df84ba08b2de94a5b3fb.png

tirgger build, 成功之后就能看到构建成功过的包,下载使用

7fb70b427b01ff2d1ae3969a9951dc83.png

总结

使用 Electron,前端开发者可以使用自己的武器构建跨系统的桌面端应用,而且不用学习其他技术,缺点是一个小小的功能打包完的体积是 70M。

这个 app 从有想法到最终实现比预期的简单,感兴趣的同学也可以自己 DIY 些小玩意儿。完整的代码在 github 上https://github.com/HelKyle/focus,欢迎体验,同时也欢迎 star~

作者:HelKyle
https://juejin.im/post/5c966596e51d454d42032ce9

23a9c963a6efb12efd62b2fd2d25126b.png

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐