Electron实现 mac 风格的关闭、最大化、最小化按钮

在开发 Electron 应用的时候,通常会将自带的"窗口控制按钮区域" 隐藏, 然后自己实现对窗口的控制。
下图是在 mac 下面的效果。
未命名.gif

首先用HTML和CSS创建窗口控制按钮

以下例子会展示了如何使用CSS的伪元素,过渡,以及其他技术来创建出非常独特的、具有交互感的窗口控制按钮。

基本结构

首先,我们需要构建HTML的基本结构。每个按钮都包含在<div>标签内,并且都有一个btn类,以及各自特有的类(例如close-btnmin-btnmax-btn)。这就让我们能够在CSS中针对性地进行样式修改。

<div class="wrapper">
    <div class="btn close-btn"></div>
    <div class="btn min-btn"></div>
    <div class="btn max-btn"></div>
</div>

CSS样式

接着,我们用CSS为每个按钮添加样式。所有按钮都有一些共享的样式,例如宽度、高度和边界半径等。每个按钮还有自己的背景颜色和边界颜色。

.btn {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    margin-right: 6px;
    position: relative;
    overflow: hidden;
    cursor: pointer;
}

.close-btn {
    background: #FF5D5B;
    border: 1px solid #CF544D;
}

.min-btn {
    background: #FFBB39;
    border: 1px solid #CFA64E;
}

.max-btn {
    background: #00CD4E;
    border: 1px solid #0EA642;
}

利用伪元素添加图标

然后,我们利用伪元素为按钮添加图标。::before::after伪元素在按钮内创建了两个额外的元素,我们可以利用这些元素创建按钮的图标。例如,关闭按钮的图标是一个红色的“X”,最小化按钮的图标是一个黄色的“-”,最大化按钮的图标是一个绿色的“+”。

/* Close btn */
.close-btn:before, .close-btn:after {
    width: 1px;
    height: 70%;
    background: #460100;
}

.close-btn:before {
    transform: translate(-50%, -50%) rotate(45deg);
}

.close-btn:after {
    transform: translate(-50%, -50%) rotate(-45deg);
}

/* min btn */
.min-btn:before {
    width: 70%;
    height: 1px;
    background: #460100;
}

/* max btn */
.max-btn:before {
    width: 50%;
    height: 50%;
    background: #024

代码演示

为了方便,我将这部分代码放到一个页面上了
点击这里 可以查看,全部源码可以在 chrome 的控制台看到。

将上面的代码用在 electron +vue3 中

先上代码,实现一个组件,这个组件实现了最大化、最小化、关闭等事件。

<script setup lang="ts">
import { onMounted, ref, onUnmounted } from "vue";
import { ipcRenderer } from "electron";
defineProps<{ title?: strng }>();

let hover = ref('');

//关闭窗口
let closeWindow = () => {
   ipcRenderer.invoke("closeWindow");
};

//最大化窗口
let maxmizeMainWin = () => {
   ipcRenderer.invoke("maxmizeWindow");

};

//最小化窗口
let minimizeMainWindow = () => {
   ipcRenderer.invoke("minimizeWindow");
};

//窗口最大化事件
let winMaximizeEvent = () => {
   console.log('max')
};


onMounted(() => {
   ipcRenderer.on("windowMaximized", winMaximizeEvent);
});

onUnmounted(() => {
   ipcRenderer.off("windowMaximized", winMaximizeEvent);
});


</script>
 
<template>
   <div class="wrapper">
       <div class="btn close-btn" @click="closeWindow" @mouseover="hover = 'close'" @mouseleave="hover = ''"></div>
       <div class="btn min-btn" @click="minimizeMainWindow" @mouseover="hover = 'min'" @mouseleave="hover = ''"></div>
       <div class="btn max-btn" @click="maxmizeMainWin" @mouseover="hover = 'max'" @mouseleave="hover = ''"></div>
   </div>
</template>
 
<style scoped>
body {
   margin: 0;
}

.wrapper {
   height: 32px;
   background: rgb(46, 46, 46);
   display: flex;
   justify-content: center;
   align-items: center;
}

.btn {
   width: 12px;
   height: 12px;
   border-radius: 50%;
   margin-right: 6px;
   position: relative;
   overflow: hidden;
   cursor: pointer;
}

.btn:last-child {
   margin-right: 0;
}

.btn:before,
.btn:after {
   content: "";
   position: absolute;
   top: 100%;
   left: 50%;
   transform: translate(-50%, -50%);
   border-radius: 1px;
   opacity: 0;
   transition: all 300ms ease-in-out;
}

.close-btn {
   background: #FF5D5B;
   border: 1px solid #CF544D;
}

.min-btn {
   background: #FFBB39;
   border: 1px solid #CFA64E;
}

.max-btn {
   background: #00CD4E;
   border: 1px solid #0EA642;
}

/* Close btn */
.close-btn:before,
.close-btn:after {
   width: 1px;
   height: 70%;
   background: #460100;
}

.close-btn:before {
   transform: translate(-50%, -50%) rotate(45deg);
}

.close-btn:after {
   transform: translate(-50%, -50%) rotate(-45deg);
}

/* min btn */
.min-btn:before {
   width: 70%;
   height: 1px;
   background: #460100;
}

/* max btn */
.max-btn:before {
   width: 50%;
   height: 50%;
   background: #024D0F;
}

.max-btn:after {
   width: 1px;
   height: 90%;
   transform: translate(-50%, -50%) rotate(-135deg);
   background: #00CD4E;
}

/* Hover function */
.wrapper:hover .btn:before,
.wrapper:hover .btn:after {
   top: 50%;
   opacity: 1;
}
</style>

其中closeWindow,maxmizeWindow, minimizeWindow等还需要在主进程注册, 可以参考下面代码。

    ipcMain.handle("minimizeWindow", (e) => {
      this.getWin(e)?.minimize();
    });

    ipcMain.handle("maxmizeWindow", (e) => {
      const win = BrowserWindow.getFocusedWindow();
      if(win) {
        if (process.platform === 'darwin') {
          this.getWin(e)?.setFullScreen(true);
        } else {
          this.getWin(e)?.maximize();
        }
      }
    });

    ipcMain.handle("unmaximizeWindow", (e) => {
     
      const win = BrowserWindow.getFocusedWindow();
      if(win) {
        if (process.platform === 'darwin') {
          this.getWin(e)?.setFullScreen(false);
        } else {
          this.getWin(e)?.unmaximize();
        }
      }
    });

当最大化的时候,可以隐藏,调用原生的窗口控制按钮。这样就不用实现如下的效果了。
CleanShot 2023-05-18 at 15.59.24@2x.png
监听窗口最大化事件,用v-if即可实现,在此不再过多赘述。


已发布

分类

作者:

标签

评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注