HTML5 是否允许拖放上传文件夹或文件夹树?

新手上路,请多包涵

我还没有看到任何这样做的例子。 API规范中不允许这样做吗?

我正在寻找一种简单的拖放解决方案来上传整个照片文件夹树。

原文由 michael 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.5k
2 个回答

现在可以,感谢 Chrome >= 21。

 function traverseFileTree(item, path) {
  path = path || "";
  if (item.isFile) {
    // Get file
    item.file(function(file) {
      console.log("File:", path + file.name);
    });
  } else if (item.isDirectory) {
    // Get folder contents
    var dirReader = item.createReader();
    dirReader.readEntries(function(entries) {
      for (var i=0; i<entries.length; i++) {
        traverseFileTree(entries[i], path + item.name + "/");
      }
    });
  }
}

dropArea.addEventListener("drop", function(event) {
  event.preventDefault();

  var items = event.dataTransfer.items;
  for (var i=0; i<items.length; i++) {
    // webkitGetAsEntry is where the magic happens
    var item = items[i].webkitGetAsEntry();
    if (item) {
      traverseFileTree(item);
    }
  }
}, false);

更多信息: https ://protonet.info/blog/html5-experiment-drag-drop-of-folders/

原文由 Christopher Blum 发布,翻译遵循 CC BY-SA 3.0 许可协议

不幸的是,现有的答案都不是完全正确的,因为 readEntries 不一定会返回给定目录的 所有(文件或目录)条目。这是 API 规范的一部分(请参阅下面的文档部分)。

要真正获取 所有 文件,我们需要重复调用 readEntries (对于我们遇到的每个目录),直到它返回一个空数组。如果我们不这样做,我们将错过目录中的一些文件/子目录,例如在 Chrome 中, readEntries 一次最多只返回 100 个条目。

使用 Promises ( await / async ) 来更清楚地展示 readEntries 的正确用法,以及 BFS-first 遍历它(因为 BFS 是异步的)目录结构:

 // Drop handler function to get all files
async function getAllFileEntries(dataTransferItemList) {
  let fileEntries = [];
  // Use BFS to traverse entire directory/file structure
  let queue = [];
  // Unfortunately dataTransferItemList is not iterable i.e. no forEach
  for (let i = 0; i < dataTransferItemList.length; i++) {
    // Note webkitGetAsEntry a non-standard feature and may change
    // Usage is necessary for handling directories
    queue.push(dataTransferItemList[i].webkitGetAsEntry());
  }
  while (queue.length > 0) {
    let entry = queue.shift();
    if (entry.isFile) {
      fileEntries.push(entry);
    } else if (entry.isDirectory) {
      queue.push(...await readAllDirectoryEntries(entry.createReader()));
    }
  }
  return fileEntries;
}

// Get all the entries (files or sub-directories) in a directory
// by calling readEntries until it returns empty array
async function readAllDirectoryEntries(directoryReader) {
  let entries = [];
  let readEntries = await readEntriesPromise(directoryReader);
  while (readEntries.length > 0) {
    entries.push(...readEntries);
    readEntries = await readEntriesPromise(directoryReader);
  }
  return entries;
}

// Wrap readEntries in a promise to make working with readEntries easier
// readEntries will return only some of the entries in a directory
// e.g. Chrome returns at most 100 entries at a time
async function readEntriesPromise(directoryReader) {
  try {
    return await new Promise((resolve, reject) => {
      directoryReader.readEntries(resolve, reject);
    });
  } catch (err) {
    console.log(err);
  }
}

Codepen 上的完整工作示例: https ://codepen.io/pen/QWmvxwV

FWIW 我之所以选择这个,是因为在使用接受的答案时,我没有在包含 40,000 个文件的目录(许多目录包含超过 100 个文件/子目录)中取回我期望的所有文件。

文档:

此行为记录在 FileSystemDirectoryReader 中。摘录重点补充:

读取条目()

返回一个数组,其中包含 一定数量的 _目录条目_。数组中的每一项都是一个基于 FileSystemEntry 的对象——通常是 FileSystemFileEntry 或 FileSystemDirectoryEntry。

但公平地说,MDN 文档可以在其他部分更清楚地说明这一点。 readEntries() 文档简单地指出:

readEntries() 方法检索正在读取的目录中的目录条目,并将它们以数组形式传递给提供的回调函数

唯一需要多次调用的提及/提示是在 successCallback 参数的描述中:

如果没有剩余文件,或者您已经在此 FileSystemDirectoryReader 上调用了 readEntries(),则该数组为空。

可以说 API 也可以更直观。

还值得注意的是 DataTransferItem.webkitGetAsEntry() 是一个非标准功能,可能会更改,例如重命名 getAsEntry() 。它的使用对于处理嵌套在目录中的上传文件是必要的。

有关的:

  • johnozbay 评论 说,在 Chrome 上, readEntries 将返回最多 100 个目录条目(从 Chrome 64 开始验证)。
  • Xan 在这个 答案 中很好地解释了 readEntries 的正确用法(尽管没有代码)。
  • Pablo Barría Urenda 的答案 在没有 BFS 的情况下以异步方式正确调用了 readEntries 。他还指出,Firefox 会返回一个目录中的所有条目(与 Chrome 不同),但我们不能依赖给定的规范。

原文由 xlm 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题