之前一直使用的是 custom-electron-titlebar 这个库,但是因为electron版本不兼容,以及自定义的关系,决定还是自己动手写一个吧
取消electron自带的titlebar
js
const win = new BrowserWindow({
width: 800,
height: 600,
frame: false, // 取消自带的titlebar
webPreferences: {
preload,
webSecurity: false,
nodeIntegration: true,
contextIsolation: false,
},
})
在preload/index.ts里实现自定义titlebar
我们同样使用vue、jsx实现,因为titlebar比较简单,就不需要引入其他库了
为什么要在preload里实现
其实在应用代码里也可以实现,如果你的应用只会加载自己的页面的话没什么影响,但是如果通过修改href跳转到其他网站,就会出现没有顶部控制条的情况。 preload是每个窗口都会加载的,保证窗口一定有控制条。
使用vite编译preload/index.ts
因为我们是tsx写的,所以需要编译
js
// scripts/build-preload.js
const path = require('path')
const { build } = require('vite')
const { builtinModules } = require('module')
const jsx = require('@vitejs/plugin-vue-jsx')
async function buildPreload(watch) {
const child = await build({
configFile: false, // 不继承根目录vite.config配置
publicDir: false, // 编译结果不需要拷贝public文件夹
root: path.join(__dirname, '..'),
base: './',
plugins: [jsx()], // 支持jsx
build: {
outDir: './dist_preload',
assetsDir: '', // 这个要格外小心,使用默认的 assets 会导致在 Electron 打包后基于 file:// 加载文件失败
minify: false,
rollupOptions: {
// 入口
input: {
preload: path.resolve(__dirname, '../preload/index.ts'),
},
// electron 目前只支持 CommonJs 格式
output: {
format: 'cjs',
entryFileNames: '[name].js',
},
// 不打包内置的包,例如 fs、path...
external: [
'electron',
'@electron/remote',
...builtinModules,
],
},
// 一般传 {},每次preload有变化都会重新编译
watch,
},
optimizeDeps: {
exclude: ['electron'], // 告诉 Vite 排除预构建 electron,不然会出现 __diranme is not defined
},
})
return child
}
实现preload/titlebar.tsx
为了方便大家理解,我删掉了大部分的业务代码,保留了主题结构,基本就是开发一个vue组件,再结合一点electron窗口api
js
import { onMounted, ref, defineComponent } from 'vue'
import { getCurrentWindow, nativeImage } from '@electron/remote'
const win = getCurrentWindow()
export default defineComponent({
props: ['options'],
setup(props) {
const { options } = props
const state = ref()
// logo
const logo = nativeImage.createFromPath(options.icon as string).toDataURL()
// 菜单
const menuList = DefaultMenu
onMounted(() => {
updateMaximized()
win.on('maximize', updateMaximized)
win.on('unmaximize', updateMaximized)
})
function updateMaximized() {}
function onMinimize() {}
function onMaximize() {}
function onClose() {}
function onClickMenu(e: Event, item: MenuItem) {}
function onMouseOverMenu(item: MenuItem) {}
function onClickMenuItem(e: Event, item: any) {}
return () => {
const { ifShowDropdown, hoverListId } = state.value
return (
<>
<div class="logo">
<img src={logo} width="20" height="20" />
</div>
<div class="menu"></div>
<div class="title"></div>
<div class="controls"></div>
</>
)
}
},
})
给titlebar填加样式
样式文件需要在preload一加载的时候就添加到dom里
-
新建样式文件prelaod/style.css
-
在preload/index.ts中引入
js
// vite 会把带raw标志的css文件处理成字符串
import defaultStyle from './style.css?raw'
function styled(strs: string) {
const $style = document.createElement('style')
$style.innerHTML = strs
document.head.appendChild($style)
}
在preload/index.ts里初始化titlebar
js
import { createApp } from 'vue'
import Titlebar from './titlebar'
/**
* dom loaded后才能操作dom
*/
window.addEventListener('DOMContentLoaded', () => {
initTitlebar()
})
/**
* 初始化titlebar, 返回 titlebar组件实例
*/
function initTitlebar(cusOptions: Options = {}) {
const options = {
disableTitle: false,
...cusOptions,
}
// init style
styled(defaultStyle)
// 创建titlebar dom节点
const $titlebar = document.createElement('div')
document.body.insertBefore($titlebar, document.body.firstChild)
// 将Titlebar组件插入到dom里, 并返回组件实例
return createApp(Titlebar, { options }).mount($titlebar)
}
从外部控制titlebar
有的时候,我们还需要从应用里面控制titlebar的状态,这个时候是和操作vue组件实例是一样的
- 组件暴露方法
js
setup(props, { expose }) {
const state = ref()
expose({
changeState() {
....
}
})
}
- 通过titlebar实例调用
js
const $titlebar = initTitlebar()
$titlebar.changeState()
到这里,我们基本实现了titlebar,剩下的就是调整我们原来app的样式,要给顶部的titlebar留出一定的高度。