איך גוררים ומשחררים ספריות

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

הממשקים של גרירה ושחרור 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.webkitGetAsEntry() לא סטנדרטית

השיטה DataTransferItem.webkitGetAsEntry() מחזירה את הערך FileSystemFileEntry של פריט הגרירה אם הפריט הוא קובץ, ואת הערך FileSystemDirectoryEntry אם הפריט הוא ספרייה. למרות שניתן לקרוא את הקובץ או הספרייה, לא ניתן להשיב להם. לשיטה הזו יש חיסרון שאינו נמצא במסלול תקני, אבל יש לה יתרון בכך שהיא תומכת בספריות.

תמיכה בדפדפן

  • 13
  • 14
  • 50
  • 11.1

מקור

שיפור הדרגתי

בקטע הקוד שבהמשך יש שימוש בשיטה 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`;
    }
  }
});