新用户购买《Electron + Vue 3 桌面应用开发》,加小册专属微信群,参与群抽奖,送《深入浅出Electron》、《Electron实战》作者签名版。
- 1等奖:《深入浅出Electron》+《Electron实战》
- 2等奖:《深入浅出Electron》
- 3等奖:《Electron实战》
抽奖活动是掘金组织的,仅限近几日加入微信群的新成员(目前人还不多),我负责抽奖、邮寄,2022年11月20日开始抽奖。凡参与抽奖的读者都有机会中奖。
现在市场上有一些应用以其特殊的窗口造型,提供更好的用户体验。比如360浏览器的安装画面是一个圆形窗口。很多游戏的启动登录画面是一个不规则窗口,本节我就带领大家实现一个基于Electron的不规则窗口。
首先,把窗口的高度(height)和宽度(width)值修改为相同的值,使窗口成为一个正方形。
其次,把窗口的透明属性(transparent)设置为true,这样设置之后窗口还是正方形的,但只要我们控制好内容区域的Dom元素的形状,就可以让窗口看起来像一个不规则形状一样。
不规则窗口往往需要自定义边框和标题栏,所以frame也设置为false。
另外,透明的窗口不可调整大小。所以将resizable属性设置为false。
窗口显示后,为了防止双击窗口可拖拽区触发最大化事件,我们把maximizable属性也设置为false。
最终创建窗口的代码如下:
win = new BrowserWindow({
width: 380,
height: 380,
transparent: true,
frame: false,
resizable: false,
maximizable: false,
//...
})
接下来再修改App.vue的样式,使内容区域的Dom元素呈现一个圆形:
html,body {
margin: 0px;
padding: 0px;
pointer-events: none;
}
#app {
box-sizing: border-box;
width: 380px;
height: 380px;
border-radius: 190px;
border: 1px solid green;
background: #fff;
overflow: hidden;
pointer-events: auto;
}
上面样式代码中通过border-radius样式把#app元素设置成了圆形。border-radius负责定义一个元素的圆角样式,如果圆角足够大,整个DIV就变成了一个圆形。pointer-events样式,在下一个小节会有讲解。
为了调试方便,我在Vue的图标上设置了样式:-webkit-app-region: drag。这样拖拽Vue图标时也就是拖拽窗口了。
最终实现的窗口界面如图:
如果你略微了解CSS,你会知道除了圆形,你还可以通过CSS样式控制这个窗口成为任意其他形状。
上面这个应用会有一点小问题,虽然窗口看起来是圆形的,但它其实还是一个正方形窗口,只不过正方形四个角是透明的,所以看起来像一个圆形的窗口。
当我点击下图中的①区域内的文本文件时,鼠标的点击事件还是发生在本窗口内,而不会点击到那个文件上。
作为开发者,我们知晓其中的道理,但作为用户来说,这就显得很诡异。为了达到更好的用户体验,我们需要让鼠标在这4个区域发生点击动作时,点击动作可以穿透本窗口,落在窗口后面的内容上。
Electron官方文档说“不能点击穿透透明区域”,这并没有难倒我们,有一个小trick来解决这个问题。
首先,需要用到窗口对象的setIgnoreMouseEvents方法,该方法可以使窗口忽略窗口内的所有鼠标事件,并且在此窗口中发生的所有鼠标事件都将被传递到此窗口背后的内容。
如果调用该方法时传递了forward参数,如:setIgnoreMouseEvents(true, { forward: true }),则只有点击事件会穿透窗口,鼠标移动事件仍会触发。
基于此,我们为App.vue组件增加mounted钩子,代码如下:
mounted() {
const remote = require("electron").remote;
let win = remote.getCurrentWindow();
window.addEventListener("mousemove", event => {
let flag = event.target === document.documentElement;
if (flag){
win.setIgnoreMouseEvents(true, { forward: true });
}
else {
win.setIgnoreMouseEvents(false);
}
});
win.setIgnoreMouseEvents(true, { forward: true });
}
上面的代码中,设置窗口对象监听mousemove事件,当鼠标移入窗口圆形内容区的时候,不允许鼠标事件穿透。当鼠标移入透明区时,允许鼠标事件穿透。
接着我们为html,body元素增加样式:pointer-events: none,为#app元素增加样式pointer-events: auto。
设定pointer-events: none后,其所标志的元素就永远不会成为鼠标事件的target了。为子元素#app设置了pointer-events: auto,说明子元素#app还是可以成为鼠标事件的target的。也就是说除了圆形区域内可以接收鼠标事件外,其他部分将不再接收鼠标事件。当鼠标在圆形区域外移动时,窗口对象的mousemove事件触发,event.target为document.documentElement对象(这个事件并不是在html或body元素上触发的,而是在窗口对象上触发的,document.documentElement就是DOM树中的根元素,也就是html节点所代表的元素)。
至此,上文代码中的判断成立,当鼠标在前文所述四个区域移动时,鼠标事件允许穿透。鼠标在圆形区域移动时,鼠标事件不允许穿透。
至此,上文所述判断成立,运行程序,鼠标在正方形四角区域内点击,鼠标事件具备了穿透效果。