HTML ドラッグ&ドロップ インターフェースを使用すると、ウェブ アプリケーションでウェブページ上のドラッグ&ドロップされたファイルを受け入れることができます。ドラッグ&ドロップ操作中、ドラッグされたファイル アイテムとディレクトリ アイテムは、それぞれファイル エントリとディレクトリ エントリに関連付けられます。ファイルをブラウザにドラッグ&ドロップする方法は、モダンな方法とクラシックな方法の 2 つがあります。
最新の方法
File System Access API の DataTransferItem.getAsFileSystemHandle() メソッドを使用する
DataTransferItem.getAsFileSystemHandle() メソッドは、ドラッグされたアイテムがファイルの場合は FileSystemFileHandle オブジェクトを含む Promise を返し、ドラッグされたアイテムがディレクトリの場合は FileSystemDirectoryHandle オブジェクトを含む Promise を返します。これらのハンドルを使用すると、ファイルまたはディレクトリの読み取りと、必要に応じて書き戻しを行うことができます。ドラッグ&ドロップ インターフェースの 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`;
}
}
});