프레임리스 윈도우 — 커스텀 타이틀바 구현
"Spotify, VS Code, Discord는 모두 커스텀 타이틀바를 사용한다" — 브랜드 아이덴티티를 강화하고 UI를 통일하려면 프레임리스 윈도우가 필요합니다.
프레임리스 윈도우 생성
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
<!-- 커스텀 타이틀바 -->
<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>
.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;
}
윈도우 컨트롤 버튼 연결
// 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();
});
// 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()}`);
더블클릭으로 최대화
// 타이틀바 더블클릭 시 최대화/복원
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를 보여줘야 함 (플랫폼별 분기)
- 더블클릭 최대화, 최대화 상태 아이콘 변경 등 네이티브 동작 재현 필요
프레임리스 윈도우를 만들었으면, 다음은 테스트 전략을 알아봅시다.
댓글 로딩 중...