ABOUT ME

-

  • [ Electrion + Vue ] 커스텀 Navigation Bar ( Title Bar 만들기 )
    Desktop App/Electron + Vue 2025. 6. 15. 01:41
    반응형

    electron app을 만들다 보면 맥과 윈도우 둘다 고려했을때 윗 부분이 다르기때문에 똑같이 만들어주기 위해 직접 네비게이션바를 만들어주어야한다.


    Title Bar Component

    우선 타이틀바 컴포넌트를 만들어 앱의 메인 화면들이 나오는 곳에 맨 상단에 붙힌다. 항상 상단에는 타이틀이 나오도록

    <script setup lang="ts">
    
    </script>
    
    <template>
    <div class="app-title-wrapper">
      <div class="title-name">
        타이틀
      </div>
      <div class="title-control">
        <div class="symbol yellow" @click="onClickMinimize"/>
        <div class="symbol red" @click="onClickClose"/>
      </div>
    </div>
    </template>
    
    <style scoped lang="scss">
    .app-title-wrapper {
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      padding: 0 10px;
      background: var(--app-theme-background);
      color: var(--app-theme-titlebar-text-color);
      border-bottom: 0.5px solid var(--app-theme-titlebar-background);
    
      .title-name {
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: center;
      }
    
      .title-control {
        display: flex;
        gap: 6px;
        flex-shrink: 0;
    
        .symbol {
          width: 11px;
          height: 11px;
          border-radius: 50%;
          cursor: pointer;
    
          &.red {
            background: #FE5F57;
          }
    
          &.yellow {
            background: #FEBC2E;
          }
        }
      }
    }
    
    </style>

     

    최대화 아이콘은 사용하지않고, 최소화와 닫기 아이콘만 사용할려고 해서 두개만 추가했다.

     


    preload.js

    electron의 닫기와 최소화 기능 구현을 위해 preload를 만들어 electron config에 붙혀준다.

    더보기

    Electron에서 preload.js 렌더러 프로세스(=브라우저 환경, 예: Vue/React)와 메인 프로세스(Node.js/Electron API) 사이를 안전하게 연결해주는 브리지 역할을 한다.

    따라서 preload에 최소화와 닫기 아이콘을 눌렀을때 실행될 브리지를 만들어준다.

    이러면 vue에서 minimizeWindow를 실행시키면 electron의 'minimize-window'로 메시지를 보내준다.

    const { contextBridge, ipcRenderer } = require('electron');
    
    contextBridge.exposeInMainWorld('electronAPI', {
        minimizeWindow: () => ipcRenderer.send('minimize-window'),
        closeWindow: () => ipcRenderer.send('close-window')
    });

     

    이때 preload는 js다 보니 실제 사용할때 vue에서는 타입스크립트 에러가 발생한다.

    따라서 types 폴더 안에 electron 타입을 만들어서 타입 에러가 안나오도록 타입을 지정

    export interface ElectronAPI {
        minimizeWindow: () => void;
        closeWindow: () => void;
    }
    
    // 전역 선언
    declare global {
        interface Window {
            electronAPI: ElectronAPI;
        }
    }

     

    이후 electron의 main.js에 해당 preload.js를 추가시켜준다.

    import { join, dirname } from 'path';
    import { fileURLToPath } from 'url';
    
    const __filename = fileURLToPath(import.meta.url);
    const __dirname = dirname(__filename);
    
    let MainWin;
    
    function createWindow() {
        MainWin = new BrowserWindow({
            width: 1024,
            height: 512,
            webPreferences: {
                preload: join(__dirname, 'preload.js'), // preload 위치 확인
                contextIsolation: true,
                nodeIntegration: false,
            },
            //...
            }
          }

     

    그럼 이제 electron에서 최소화와 닫기 기능인 ipc를 등록한다.

    이러면 preload에서 ipcRenderer.send가 실행되면 각각의 맞는 명칭의 ipc에 실행된다.

     

    이제 여기서 frame: false로 설정하면 기존의 타이틀 바가 사라지게 된다. 타이틀 바를 커스텀을 할때는 이 속성을 무조건 false로 해야한다. 

    import { app, ipcMain ,BrowserWindow } from 'electron';
    import { join, dirname } from 'path';
    import { fileURLToPath } from 'url';
    
    const __filename = fileURLToPath(import.meta.url);
    const __dirname = dirname(__filename);
    
    let MainWin;
    
    function createWindow() {
        MainWin = new BrowserWindow({
            width: 1024,
            height: 512,
            webPreferences: {
                preload: join(__dirname, 'preload.js'), // preload 위치 확인
                contextIsolation: true,
                nodeIntegration: false,
            },
            frame: false // 타이틀바 커스텀 시 필요
        });
    
        const isDev = !app.isPackaged;
        if (isDev) {
            MainWin.loadURL('http://localhost:5000'); // Vite dev server
        } else {
            MainWin.loadFile(join(__dirname, '../dist/index.html')); // 정적 파일
        }
    
        ipcMain.on('minimize-window', () => {
            if (MainWin) MainWin.minimize();
        });
    
        ipcMain.on('close-window', () => {
            if (MainWin) MainWin.close();
        });
    }

    Vue

    렌더러 프로세스인 vue에서 preload에 등록한 minimize와 close를 실행시키면  electron app 최소화와 닫기 기능이 정상적으로 작동한다.

    <script setup lang="ts">
    
    const onClickMinimize = () => {
      window.electronAPI?.minimizeWindow()
    }
    
    const onClickClose = () => {
      window.electronAPI?.closeWindow()
    }
    
    </script>
    
    <template>
    <div class="app-title-wrapper">
      <div class="title-name">
        타이틀
      </div>
      <div class="title-control">
        <div class="symbol yellow" @click="onClickMinimize"/>
        <div class="symbol red" @click="onClickClose"/>
      </div>
    </div>
    </template>

     

    이제 기존의 타이틀 바가 사라졌기때문에 만든 타이틀바를 눌러서 electron app을 드래그 앤 드롭을 할 수 있도록 처리를해야한다.

    이때 electron의 main에서 frame을 false가 필수인 이유이다. 

    -webkit-app-region: drag;

     

    style에 -webkit-app-region: drag; 만 넣어주면 해당 영역을 눌러서 드래그 앤 드롭을 할 수 있다.

    이때 닫기나 최소화 아이콘을 눌렀을때는 드래그 앤 드롭을 막기위해 no-drag로 드래그 앤 드롭을 막는다.

    <template>
    <div class="app-title-wrapper">
      <div class="title-name">
        타이틀
      </div>
      <div class="title-control">
        <div class="symbol yellow" @click="onClickMinimize"/>
        <div class="symbol red" @click="onClickClose"/>
      </div>
    </div>
    </template>
    
    <style scoped lang="scss">
    .app-title-wrapper {
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      padding: 0 10px;
      background: var(--app-theme-background);
      color: var(--app-theme-titlebar-text-color);
      border-bottom: 0.5px solid var(--app-theme-titlebar-background);
      -webkit-app-region: drag;
    
      .title-name {
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: center;
      }
    
      .title-control {
        display: flex;
        gap: 6px;
        flex-shrink: 0;
    
        .symbol {
          width: 11px;
          height: 11px;
          border-radius: 50%;
          cursor: pointer;
          -webkit-app-region: no-drag;
    
          &.red {
            background: #FE5F57;
          }
    
          &.yellow {
            background: #FEBC2E;
          }
        }
      }
    }
    
    </style>

     

     

    그럼 최종적으로 기존의 타이틀 바는 사라지고 직접 만든 타이틀 바가 생성되며 최소화 닫기 그리고 드래그 앤 드롭 기능이 정상적으로 된다.

    728x90
    반응형

    'Desktop App > Electron + Vue' 카테고리의 다른 글

    [ Electron + Vue ] 초기 환경 세팅  (0) 2025.05.06