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

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

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

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

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

Browser Support

  • Chrome: 86.
  • Edge: 86.
  • Firefox: not supported.
  • Safari: not supported.

Source

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

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

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

Browser Support

  • Chrome: 11.
  • Edge: 12.
  • Firefox: 50.
  • Safari: 5.1.

Source

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

تستخدم المقتطفات أدناه طريقة 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`;
    }
  }
});