איך גוררים ומשחררים קבצים

תומאס שטיינר
תומאס סטיינר

הממשקים של גרירה ושחרור HTML מאפשרים לאפליקציות אינטרנט לקבל קבצים שגרירה ושחרור בדף אינטרנט. במהלך פעולת גרירה ושחרור, הגרירה של פריטי הקובץ והספרייה משויכים לרשומות בקובץ ולרשומות בספרייה, בהתאמה. יש שתי דרכים לעשות זאת: גרירה ושחרור של קבצים בדפדפן: המודרנית והקלאסית.

הדרך המודרנית

שימוש בשיטת DataTransferItem.getAsFileSystemHandle() של File System Access API

השיטה DataTransferItem.getAsFileSystemHandle() מחזירה הבטחה עם אובייקט FileSystemFileHandle אם הפריט שנגרר הוא קובץ, והבטחה עם אובייקט FileSystemDirectoryHandle אם הפריט שנגרר הוא ספרייה. נקודות האחיזה האלה מאפשרות לכם לקרוא את הקובץ או הספרייה, ואפשר גם לכתוב חזרה. הערה: הערך DataTransferItem.kind בממשק הגרירה והשחרור יהיה "file" עבור קבצים וגם ספריות. לעומת זאת, הערך FileSystemHandle.kind ב-File System Access API יהיה "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`;
    }
  }
});