"Spotify, VS Code, Discord는 모두 커스텀 타이틀바를 사용한다" — 브랜드 아이덴티티를 강화하고 UI를 통일하려면 프레임리스 윈도우가 필요합니다.


프레임리스 윈도우 생성

JAVASCRIPT
const win = new BrowserWindow({
  width: 1200,
  height: 800,
  frame: false,           // OS 기본 타이틀바 제거
  transparent: false,     // 투명 배경 (필요 시)
  titleBarStyle: 'hidden', // macOS: 타이틀바 숨기되 신호등 버튼 유지
  // titleBarStyle: 'hiddenInset', // macOS: 신호등 버튼을 콘텐츠 안으로
  webPreferences: {
    preload: path.join(__dirname, 'preload.js'),
  },
});

macOS titleBarStyle 옵션

설명
default기본 타이틀바
hidden타이틀바 숨기고 신호등 버튼만 표시
hiddenInset신호등 버튼을 콘텐츠 영역 안으로 이동
customButtonsOnHover호버 시에만 신호등 표시

커스텀 타이틀바 HTML/CSS

HTML
<!-- 커스텀 타이틀바 -->
<div class="titlebar" id="titlebar">
  <div class="titlebar-drag-region"></div>
  <div class="titlebar-title">내 앱</div>
  <div class="titlebar-controls">
    <button id="btn-minimize" class="titlebar-btn">
      <svg viewBox="0 0 12 12"><rect y="5" width="12" height="2"/></svg>
    </button>
    <button id="btn-maximize" class="titlebar-btn">
      <svg viewBox="0 0 12 12"><rect x="1" y="1" width="10" height="10" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>
    </button>
    <button id="btn-close" class="titlebar-btn btn-close">
      <svg viewBox="0 0 12 12"><path d="M1,1 L11,11 M1,11 L11,1" stroke="currentColor" stroke-width="1.5"/></svg>
    </button>
  </div>
</div>
CSS
.titlebar {
  display: flex;
  align-items: center;
  height: 32px;
  background: var(--titlebar-bg, #2d2d2d);
  color: var(--titlebar-fg, #cccccc);
  user-select: none;
  position: relative;
}

/* 드래그 가능 영역 — 이 영역을 잡고 윈도우를 이동 */
.titlebar-drag-region {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  -webkit-app-region: drag;  /* 핵심: 이 CSS로 드래그 가능 */
}

.titlebar-title {
  flex: 1;
  text-align: center;
  font-size: 13px;
  pointer-events: none;
}

.titlebar-controls {
  display: flex;
  -webkit-app-region: no-drag;  /* 버튼은 드래그 불가 */
  z-index: 1;
}

.titlebar-btn {
  width: 46px;
  height: 32px;
  border: none;
  background: transparent;
  color: inherit;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.titlebar-btn:hover {
  background: rgba(255, 255, 255, 0.1);
}

.btn-close:hover {
  background: #e81123;
  color: white;
}

/* macOS에서는 Windows 스타일 버튼 숨기기 */
.platform-darwin .titlebar-controls {
  display: none;
}

/* macOS 신호등 버튼 공간 확보 */
.platform-darwin .titlebar {
  padding-left: 80px;
}

윈도우 컨트롤 버튼 연결

JAVASCRIPT
// preload.js
contextBridge.exposeInMainWorld('electronAPI', {
  minimize: () => ipcRenderer.send('window:minimize'),
  maximize: () => ipcRenderer.send('window:maximize'),
  close: () => ipcRenderer.send('window:close'),
  isMaximized: () => ipcRenderer.invoke('window:isMaximized'),
  onMaximizeChange: (callback) => {
    ipcRenderer.on('window:maximizeChanged', (_e, isMax) => callback(isMax));
  },
  getPlatform: () => process.platform,
});

// main.js
ipcMain.on('window:minimize', (event) => {
  BrowserWindow.fromWebContents(event.sender).minimize();
});

ipcMain.on('window:maximize', (event) => {
  const win = BrowserWindow.fromWebContents(event.sender);
  if (win.isMaximized()) {
    win.unmaximize();
  } else {
    win.maximize();
  }
});

ipcMain.on('window:close', (event) => {
  BrowserWindow.fromWebContents(event.sender).close();
});
JAVASCRIPT
// renderer.js
document.getElementById('btn-minimize').onclick = () => {
  window.electronAPI.minimize();
};

document.getElementById('btn-maximize').onclick = () => {
  window.electronAPI.maximize();
};

document.getElementById('btn-close').onclick = () => {
  window.electronAPI.close();
};

// 최대화 상태 변경 시 아이콘 업데이트
window.electronAPI.onMaximizeChange((isMaximized) => {
  const btn = document.getElementById('btn-maximize');
  btn.title = isMaximized ? '이전 크기로' : '최대화';
});

// 플랫폼별 스타일 적용
document.body.classList.add(`platform-${window.electronAPI.getPlatform()}`);

더블클릭으로 최대화

JAVASCRIPT
// 타이틀바 더블클릭 시 최대화/복원
const titlebar = document.getElementById('titlebar');
titlebar.addEventListener('dblclick', () => {
  window.electronAPI.maximize();
});

면접 포인트 정리

  • frame: false로 OS 타이틀바를 제거하고 커스텀 타이틀바 구현
  • -webkit-app-region: drag로 드래그 가능 영역 설정, 버튼은 no-drag
  • macOS는 titleBarStyle: 'hiddenInset'으로 신호등 버튼 유지 가능
  • Windows와 macOS에서 다른 UI를 보여줘야 함 (플랫폼별 분기)
  • 더블클릭 최대화, 최대화 상태 아이콘 변경 등 네이티브 동작 재현 필요

프레임리스 윈도우를 만들었으면, 다음은 테스트 전략을 알아봅시다.

댓글 로딩 중...