تتيح واجهات سحب وإفلات HTML لتطبيقات الويب قبول الملفات التي يتم سحبها وإفلاتها على صفحة ويب. أثناء عملية السحب والإفلات، يتم ربط عناصر الملفات والأدلة التي تم سحبها بإدخالات الملفات وإدخالات الأدلة على التوالي. عند سحب الملفات وإفلاتها في المتصفّح، تتوفّر طريقتان لإجراء ذلك: الطريقة الحديثة والطريقة التقليدية.
الطريقة الحديثة
استخدام طريقة DataTransferItem.getAsFileSystemHandle() في File System Access API
يعرض الإجراء DataTransferItem.getAsFileSystemHandle() وعدًا يتضمّن العنصر FileSystemFileHandle إذا كان العنصر الذي تم سحبه عبارة عن ملف، ويعرض وعدًا يتضمّن العنصر FileSystemDirectoryHandle إذا كان العنصر الذي تم سحبه عبارة عن دليل. تتيح لك هذه المعرّفات القراءة،
ويمكنك أيضًا الكتابة في الملف أو الدليل. يُرجى العِلم أنّ DataTransferItem.kind في واجهة السحب والإفلات سيكون "file" لكل من الملفات والأدلة، بينما سيكون FileSystemHandle.kind في File System Access API هو "file" للملفات و"directory" للأدلة.
الطريقة الكلاسيكية
استخدام طريقة DataTransferItem.webkitGetAsEntry() غير العادية
يعرض الإجراء DataTransferItem.webkitGetAsEntry() FileSystemFileEntry
لعنصر بيانات السحب إذا كان العنصر ملفًا، وFileSystemDirectoryEntry إذا كان العنصر دليلاً. على الرغم من أنّه يمكنك قراءة الملف أو الدليل، لا يمكنك الكتابة فيهما. تتميّز هذه الطريقة بأنّها لا تتبع مسار المعايير، ولكنّها تتيح استخدام الأدلة.
التحسين التدريجي
يستخدم المقتطف أدناه طريقة DataTransferItem.getAsFileSystemHandle() الحديثة من File System Access API عندما تكون متاحة، ثم يعود إلى طريقة 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`;
}
}
});