Electron 已经不算新技术,最早是 github 从 Atom 编辑器衍生出来的框架。通过编写 Javascript, HTML, CSS 能快速编译出跨系统的桌面 app。Electron 的出现使得作为前端开发工程师的我们输出范围更广。
分享最近用 Electron 做的一个基于番茄工作法的小应用,由于实现难度不大,市面上已经有非常多类似的app。我们尝试用 Electron 来实现一个。
最终效果预览:
? 工作法
番茄工作法的核心是将任务颗粒拆分到单位时间内(25分钟)可以完成,在这25分钟内专注在这个任务三,不允许做任何与任务无关的事,在任务任务完成之后可以短暂休息一会,再继续工作。
所以这个 app 的重点是让你创建任务,⏳ 25分钟,帮让 focus on 当前在做的任务。
站在巨人的肩膀上开发
尝试新技术的时候,不要从零开始学习如何搭建技术栈,先做出来,遇到问题再查。 Electron 社区有很多优秀的沉淀,工具,模板,组件,教程等等。
搜索 react 关键字,找到了 electron-react-boilerplate 这个样板库, 这个库已经集成了 react
, redux
, sass
, flow
, hmr
webpack
等工具,同时准备好 electron-builder
打包工具,作为 electron
新手,我们优先选择开箱即用的工具,快速开启业务开发。
SVG 和 React Component
大概画了一下草图,准备进入开发阶段。考虑后面会用到 svg icon,先在 FlatIcon 上找些免费的图标,下载 SVG 文件。
通过 SVGR 在线工具导入 svg 内容生成 React Component 代码。(svgr 也有 cli 等工具)
用 SVG Component 的好处是可以在代码上更灵活地控制样式,相比 png 图标可交互性强,复用率高。
托盘和托盘弹窗
这个 app 启动的时候就隐藏在托盘菜单的一角,点击的时候显示 BrowserWindow,通过 Electron 提供的方法,可以获得托盘和托盘弹窗的 Bounds 信息,设置坐标位置。
// main.js
const tray = new Tray(path.join(__dirname, '../static', 'tray.png'));
const mainWindow = new BrowserWindow({
// ...others
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 };
};
复制代码
?图的三角是由前端代码绘制的,加上 frame 和 electron 背景色,应该长这样。
渲染线程和主线程
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/Application Support/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 代码控制。
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 (
<svg>
...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 通知用户。
Travis CI
功能开发完毕之后,使用 electron-builder
进行打包发布,构建之后推到 github release 下,用户可以直接在这下载到最新的包。
同样的, boilerplate
已经准备好 .travis.yml
文件,唯一需要我们操作的是在 github.com/settings/to… 上生成 token,在 www.travis-ci.org/ 构建之前配置 Environment Variables
,GH_TOKEN
tirgger build, 成功之后就能看到构建成功过的包,下载使用
总结
使用 Electron,前端开发者可以使用自己的武器构建跨系统的桌面端应用,而且不用学习其他技术,缺点是一个小小的功能打包完的体积是 70M。
这个 app 从有想法到最终实现比预期的简单,感兴趣的同学也可以自己 DIY 些小玩意儿。完整的代码在 github 上github.com/HelKyle/foc…,欢迎体验,同时也欢迎 star~