드래그 앤 드롭 — 네이티브 파일 DnD 구현
"드래그 앤 드롭은 데스크톱 앱의 기본 인터랙션" — 파일을 끌어다 놓는 동작은 웹 API와 Electron의 네이티브 기능을 결합해서 구현합니다.
파일 드롭 받기 (외부 → 앱)
렌더러에서 HTML5 DnD API 사용
// renderer.js
const dropZone = document.getElementById('drop-zone');
// 기본 동작 방지 (파일을 열지 않도록)
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, (e) => {
e.preventDefault();
e.stopPropagation();
});
});
// 드래그 오버 시 시각적 피드백
dropZone.addEventListener('dragover', (e) => {
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('drag-over');
});
// 드롭 이벤트 처리
dropZone.addEventListener('drop', async (e) => {
dropZone.classList.remove('drag-over');
const files = Array.from(e.dataTransfer.files);
for (const file of files) {
console.log('드롭된 파일:', file.name, file.path);
// file.path는 Electron에서만 사용 가능 (웹에서는 없음)
await window.electronAPI.processFile(file.path);
}
});
전체 윈도우에서 드롭 방지
// 드롭 영역 밖에서 파일을 드롭하면 파일이 열리는 것 방지
document.addEventListener('dragover', (e) => e.preventDefault());
document.addEventListener('drop', (e) => e.preventDefault());
파일 드래그 내보내기 (앱 → 외부)
// main.js — 앱에서 외부로 파일 드래그
ipcMain.on('drag:start', (event, filePath) => {
event.sender.startDrag({
file: filePath,
icon: path.join(__dirname, 'assets', 'drag-icon.png'),
});
});
// preload.js
contextBridge.exposeInMainWorld('electronAPI', {
startDrag: (filePath) => ipcRenderer.send('drag:start', filePath),
});
// renderer.js
const fileItem = document.querySelector('.file-item');
fileItem.addEventListener('dragstart', (e) => {
e.preventDefault();
window.electronAPI.startDrag('/path/to/file.txt');
});
다중 파일 드롭 처리
// 여러 파일을 동시에 드롭할 때
dropZone.addEventListener('drop', async (e) => {
const files = Array.from(e.dataTransfer.files);
// 파일 필터링 (확장자 검사)
const allowedExtensions = ['.txt', '.md', '.json', '.csv'];
const validFiles = files.filter(file => {
const ext = path.extname(file.name).toLowerCase();
return allowedExtensions.includes(ext);
});
if (validFiles.length !== files.length) {
showWarning(`${files.length - validFiles.length}개 파일이 지원되지 않는 형식입니다.`);
}
// 순차 처리
for (const file of validFiles) {
await processFile(file.path);
}
});
폴더 드롭
dropZone.addEventListener('drop', async (e) => {
const items = Array.from(e.dataTransfer.items);
for (const item of items) {
const entry = item.webkitGetAsEntry();
if (entry) {
if (entry.isDirectory) {
console.log('폴더가 드롭됨:', entry.name);
// 폴더 경로를 메인 프로세스로 전달
const file = item.getAsFile();
await window.electronAPI.openFolder(file.path);
} else {
console.log('파일이 드롭됨:', entry.name);
}
}
}
});
CSS 스타일링
.drop-zone {
border: 2px dashed #ccc;
border-radius: 8px;
padding: 2rem;
text-align: center;
transition: all 0.2s ease;
}
.drop-zone.drag-over {
border-color: #4a9eff;
background-color: rgba(74, 158, 255, 0.1);
}
.drop-zone.drag-over::after {
content: '여기에 파일을 놓으세요';
display: block;
font-size: 1.2rem;
color: #4a9eff;
}
면접 포인트 정리
- 외부→앱: HTML5 DnD API 사용,
file.path는 Electron 전용 속성 - 앱→외부:
webContents.startDrag()으로 네이티브 드래그 - 전체 윈도우의
dragover/drop기본 동작을 방지해야 함 - 폴더 드롭도
webkitGetAsEntry()로 처리 가능
드래그 앤 드롭을 다뤘으면, 다음은 클립보드 API를 살펴봅시다.
댓글 로딩 중...