HTML 드래그 앤 드롭 인터페이스를 사용하면 웹 애플리케이션이 웹페이지에서 드래그 앤 드롭 파일을 허용할 수 있습니다. 드래그 앤 드롭 작업 중에 드래그한 파일 및 디렉터리 항목은 각각 파일 항목 및 디렉터리 항목과 연결됩니다. 파일을 브라우저로 드래그 앤 드롭하는 방법에는 두 가지가 있습니다. 현대적인 방법과 기존 방법입니다.
현대적인 방식
File System Access API의 DataTransferItem.getAsFileSystemHandle()
메서드 사용
DataTransferItem.getAsFileSystemHandle()
메서드는 드래그된 항목이 파일인 경우 FileSystemFileHandle
객체가 있는 프로미스를 반환하고 드래그된 항목이 디렉터리인 경우 FileSystemDirectoryHandle
객체가 있는 프로미스를 반환합니다. 이러한 핸들을 사용하면 파일이나 디렉터리를 읽고 선택적으로 다시 쓸 수 있습니다. 드래그 앤 드롭 인터페이스의 DataTransferItem.kind
는 파일 및 디렉터리 모두에서 "file"
인 반면 File System Access API의 FileSystemHandle.kind
는 파일의 경우 "file"
, 디렉터리의 경우 "directory"
입니다.
기존의 방식
비표준 DataTransferItem.webkitGetAsEntry()
메서드 사용
DataTransferItem.webkitGetAsEntry()
메서드는 항목이 파일인 경우 드래그 데이터 항목의 FileSystemFileEntry
를 반환하고 항목이 디렉터리인 경우 FileSystemDirectoryEntry
를 반환합니다. 파일이나 디렉터리를 읽을 수 있지만 다시 쓸 수 있는 방법은 없습니다. 이 방법에는 표준 트랙에 없는 단점이 있지만 디렉터리를 지원한다는 이점이 있습니다.
점진적 개선
아래 스니펫은 최신 File System Access API의 DataTransferItem.getAsFileSystemHandle()
메서드가 지원되는 경우 이를 사용하고 비표준 DataTransferItem.webkitGetAsEntry()
메서드로 대체하고 마지막으로 기본 DataTransferItem.getAsFile()
메서드로 대체합니다. 각 handle
의 유형은 다음 중 하나일 수 있으므로 확인해야 합니다.
- 최신 코드 경로가 선택된 경우
FileSystemDirectoryHandle
FileSystemDirectoryEntry
: 비표준 코드 경로가 선택된 경우
모든 유형에는 name
속성이 있으므로 로깅해도 괜찮으며 항상 작동합니다.
// Run feature detection.
const supportsFileSystemAccessAPI =
'getAsFileSystemHandle' in DataTransferItem.prototype;
const supportsWebkitGetAsEntry =
'webkitGetAsEntry' in DataTransferItem.prototype;
// This is the drag and drop zone.
const elem = document.querySelector('main');
// Prevent navigation.
elem.addEventListener('dragover', (e) => {
e.preventDefault();
});
// Visually highlight the drop zone.
elem.addEventListener('dragenter', (e) => {
elem.style.outline = 'solid red 1px';
});
// Visually unhighlight the drop zone.
elem.addEventListener('dragleave', (e) => {
elem.style.outline = '';
});
// This is where the drop is handled.
elem.addEventListener('drop', async (e) => {
// Prevent navigation.
e.preventDefault();
if (!supportsFileSystemAccessAPI && !supportsWebkitGetAsEntry) {
// Cannot handle directories.
return;
}
// Unhighlight the drop zone.
elem.style.outline = '';
// Prepare an array of promises…
const fileHandlesPromises = [...e.dataTransfer.items]
// …by including only files (where file misleadingly means actual file _or_
// directory)…
.filter((item) => item.kind === 'file')
// …and, depending on previous feature detection…
.map((item) =>
supportsFileSystemAccessAPI
// …either get a modern `FileSystemHandle`…
? item.getAsFileSystemHandle()
// …or a classic `FileSystemFileEntry`.
: item.webkitGetAsEntry(),
);
// Loop over the array of promises.
for await (const handle of fileHandlesPromises) {
// This is where we can actually exclusively act on the directories.
if (handle.kind === 'directory' || handle.isDirectory) {
console.log(`Directory: ${handle.name}`);
}
}
});
추가 자료
데모
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>How to drag and drop directories</title>
</head>
<body>
<main>
<h1>How to drag and drop directories</h1>
<p>Drag and drop one or multiple files or directories onto the page.</p>
<pre></pre>
</main>
</body>
</html>
CSS
:root {
color-scheme: dark light;
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
padding: 1rem;
font-family: system-ui, sans-serif;
line-height: 1.5;
min-height: 100vh;
display: flex;
flex-direction: column;
}
img,
video {
height: auto;
max-width: 100%;
}
main {
flex-grow: 1;
}
footer {
margin-top: 1rem;
border-top: solid CanvasText 1px;
font-size: 0.8rem;
}
JS
const supportsFileSystemAccessAPI =
"getAsFileSystemHandle" in DataTransferItem.prototype;
const supportsWebkitGetAsEntry =
"webkitGetAsEntry" in DataTransferItem.prototype;
const elem = document.querySelector("main");
const debug = document.querySelector("pre");
elem.addEventListener("dragover", (e) => {
// Prevent navigation.
e.preventDefault();
});
elem.addEventListener("dragenter", (e) => {
elem.style.outline = "solid red 1px";
});
elem.addEventListener("dragleave", (e) => {
elem.style.outline = "";
});
elem.addEventListener("drop", async (e) => {
e.preventDefault();
elem.style.outline = "";
const fileHandlesPromises = [...e.dataTransfer.items]
.filter((item) => item.kind === "file")
.map((item) =>
supportsFileSystemAccessAPI
? item.getAsFileSystemHandle()
: supportsWebkitGetAsEntry
? item.webkitGetAsEntry()
: item.getAsFile()
);
for await (const handle of fileHandlesPromises) {
if (handle.kind === "directory" || handle.isDirectory) {
console.log(`Directory: ${handle.name}`);
debug.textContent += `Directory: ${handle.name}\n`;
} else {
console.log(`File: ${handle.name}`);
debug.textContent += `File: ${handle.name}\n`;
}
}
});