كيفية سحب الملفات وإفلاتها

تمكّن واجهات السحب والإفلات بتنسيق HTML تطبيقات الويب من قبول الملفات التي تم سحبها وإفلاتها في صفحة الويب. أثناء عملية السحب والإفلات، يتم ربط عناصر الملف والدليل التي تم سحبها بإدخالات الملفات وإدخالات الدليل على التوالي. عندما يتعلق الأمر بسحب الملفات وإفلاتها في المتصفح، هناك طريقتان للقيام بذلك: الطريقة الحديثة والكلاسيكية.

الطريقة الحديثة

استخدام طريقة DataTransferItem.getAsFileSystemHandle() لـ File System Access API

تعرض الطريقة DataTransferItem.getAsFileSystemHandle() وعدًا باستخدام الكائن FileSystemFileHandle إذا كان العنصر الذي تم سحبه عبارة عن ملف، وتعرض وعدًا باستخدام كائن FileSystemDirectoryHandle إذا كان العنصر المسحوب عبارة عن دليل. تتيح لك هذه الأسماء إمكانية القراءة، ويمكنك إعادة الكتابة إلى الملف أو الدليل اختياريًا. يُرجى العِلم أنّ واجهة السحب والإفلات DataTransferItem.kind ستكون "file" لكل من الملفات والأدلة، في حين أنّ واجهة برمجة التطبيقات File System Access API FileSystemHandle.kind ستكون "file" للملفات و"directory" للأدلة.

التوافق مع المتصفح

  • 86
  • 86
  • x
  • x

المصدر

الطريقة الكلاسيكية

استخدام طريقة DataTransferItem.getAsFile() الكلاسيكية

تعرض الطريقة DataTransferItem.getAsFile() الكائن File لعنصر بيانات السحب. إذا لم يكن العنصر ملفًا، فإن هذه الطريقة تعرض null. بينما يمكنك قراءة الملف، لا توجد طريقة للرد عليه. لهذه الطريقة عيب أنها لا تدعم الأدلة.

التوافق مع المتصفح

  • 11
  • 12
  • 50
  • 5.1

المصدر

التحسين التدريجي

يستخدم المقتطف أدناه طريقة DataTransferItem.getAsFileSystemHandle() الحديثة لواجهة برمجة التطبيقات File System Access API عندما تكون متاحة، ثم يعود إلى طريقة DataTransferItem.webkitGetAsEntry() غير العادية، ثم يعود في النهاية إلى طريقة DataTransferItem.getAsFile() الكلاسيكية. تأكّد من التحقق من نوع كل handle، إذ قد يكون أيًا مما يلي:

  • FileSystemFileHandle عند اختيار مسار الترميز الحديث.
  • File عند اختيار مسار الرمز الكلاسيكي

تحتوي جميع الأنواع على السمة name، لذلك لا بأس في تسجيل الدخول وسيتم إجراء ذلك دائمًا.

// Run feature detection.
const supportsFileSystemAccessAPI =
  'getAsFileSystemHandle' 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();
  // 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 `File`.
        : item.getAsFile(),
    );
  // Loop over the array of promises.
  for await (const handle of fileHandlesPromises) {
    // This is where we can actually exclusively act on the files.
    if (handle.kind === 'file' || handle.isFile) {
      console.log(`File: ${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 files</title>
  </head>
  <body>
    <main>
      <h1>How to drag and drop files</h1>
      <p>Drag and drop one or multiple files 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`;
    }
  }
});