electron开发自定义的titlebar

之前一直使用的是 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留出一定的高度。

相关推荐
kyriewen2 小时前
别再 console.log 了:5 个 Chrome DevTools 调试技巧,用过就回不去了
前端·javascript·面试
IT_陈寒4 小时前
Python搞不定字符串编码?这破玩意坑我两小时!
前端·人工智能·后端
DigitalOcean5 小时前
Laravel 开发者已在 DigitalOcean 上开通超过 10 万台服务器
前端·laravel
星始流年6 小时前
从 Tool 到 Skill——基于 LangChain 的服务端Skill实现
前端·langchain·agent
李惟6 小时前
开源本地通信库,纯客户端 RPC,像聊天一样通信
前端
YAwu116 小时前
深入解析 React 炫彩鼠标跟随标题组件:从坐标定位到动画性能
前端·react.js
GuWenyue6 小时前
排序效率低?5分钟吃透快速排序,性能飙升至O(nlogn)
前端·javascript·面试
OpenTiny社区6 小时前
🎨 看完 GenUI SDK 源码我悟了!
前端·vue.js·github
叁两6 小时前
前端转型AI Agent该如何学习?(前置篇)
前端·人工智能·node.js
何时梦醒6 小时前
深入理解递归与快速排序 —— 从基础入门到手写实现
前端·javascript