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

mic*_*ael 71 html5 drag-and-drop file-upload

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

我正在寻找一个简单的拖放解决方案,用于上传整个文件夹树的照片.

小智 71

现在可以了,感谢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);
Run Code Online (Sandbox Code Playgroud)

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

  • 即使2年后,IE和Firefox似乎也不愿意实现这一点. (9认同)
  • 现在,对于Firefox也是如此:http://stackoverflow.com/a/33431704/195216它通过drag'n'drop和chrome和firefox中的对话框显示文件夹上传! (8认同)
  • 重要警告:此答案中的代码仅限于给定目录中的100个文件.见这里:https://bugs.chromium.org/p/chromium/issues/detail?id = 514087 (6认同)
  • Edge也支持这一点. (2认同)
  • @johnozbay不幸的是,更多的人收到了您的重要警告,这不一定是Chromium的问题,因为规范说“ readEntries”不会返回目录中的所有内容。根据您提供的错误链接,我写了一个完整的答案:/sf/answers/3714100211/ (2认同)

xlm*_*xlm 25

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

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

使用Promises(await/ async)更清楚地演示readEntries(因为它是异步的)正确用法,以及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++) {
    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);
  }
}
Run Code Online (Sandbox Code Playgroud)

关于Codepen的完整工作示例:https://codepen.io/anon/pen/gBJrOP

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

文档:

FileSystemDirectoryReader中记录了此行为.摘录重点补充:

readEntries()
返回一个包含一些 目录条目的数组.数组中的每个项目都是基于FileSystemEntry的对象 - 通常是FileSystemFileEntry或FileSystemDirectoryEntry.

但公平地说,MDN文档可以在其他部分更清楚.该readEntries()文件只是指出:

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

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

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

可以说API也可以更直观,但正如文档所述:它是非标准/实验性功能,而不是标准轨道,并且不能期望适用于所有浏览器.

有关:

  • johnozbay评论说,在Chrome上,readEntries目录最多可返回100个条目(已验证为Chrome 64).
  • XanreadEntries在这个答案中解释了正确的用法(尽管没有代码).
  • PabloBarríaUrenda的答案readEntries在没有BFS的情况下以异步方式正确调用.他还指出Firefox会返回目录中的所有条目(与Chrome不同),但我们不能依赖于此规范.

  • 我很欣赏@johnozbay我只是担心很多用户似乎忽略了这个小而重要的事实:规范/ API和这种边缘情况(目录中的100多个文件)并非不太可能.当我没有收到我预期的所有文件时,我才意识到这一点.你的评论应该是答案. (5认同)
  • 非常感谢大声喊叫,并把这些内容拿出来.SOF需要像你这样的更优秀的成员!✌ (4认同)

Mar*_*pel 13

在给HTML 5邮件列表的消息中,Ian Hickson说:

HTML5现在必须一次上传许多文件.浏览器可以允许用户一次选择多个文件,包括跨多个目录; 这有点超出了规范的范围.

(另请参阅原始功能提案.)因此,可以安全地假设他认为使用拖放操作上传文件夹也超出了范围.显然,由浏览器来提供单个文件.

正如Lars Gunther所描述的那样,上传文件夹也会遇到其他一些困难:

该提案必须有两张支票(如果可行的话):

  1. 最大尺寸,阻止某人上传数百个未压缩原始图像的完整目录...

  2. 即使省略accept属性也会过滤.应省略Mac OS元数据和Windows缩略图等.默认情况下,应排除所有隐藏文件和目录.


Kon*_*aju 10

现在,您可以通过拖放和输入上传目录.

<input type='file' webkitdirectory >
Run Code Online (Sandbox Code Playgroud)

和拖放(对于webkit浏览器).

处理拖放文件夹.

<div id="dropzone"></div>
<script>
var dropzone = document.getElementById('dropzone');
dropzone.ondrop = function(e) {
  var length = e.dataTransfer.items.length;
  for (var i = 0; i < length; i++) {
    var entry = e.dataTransfer.items[i].webkitGetAsEntry();
    if (entry.isFile) {
      ... // do whatever you want
    } else if (entry.isDirectory) {
      ... // do whatever you want
    }
  }
};
</script>
Run Code Online (Sandbox Code Playgroud)

资源:

http://updates.html5rocks.com/2012/07/Drag-and-drop-a-folder-onto-Chrome-now-available


Dan*_*rts 8

Firefox现在支持文件夹上传,截至2016年11月15日,版本为v50.0:https://developer.mozilla.org/en-US/Firefox/Releases/50#Files_and_directories

您可以将文件夹拖放到Firefox中,也可以浏览并选择要上载的本地文件夹.它还支持嵌套在子文件夹中的文件夹.

这意味着您现在可以使用Chrome,Firefox,Edge或Opera上传文件夹.您目前无法使用Safari或Internet Explorer.


gra*_*tot 8

此函数将为您提供所有已删除文件的数组的承诺,例如<input type="file"/>.files:

function getFilesWebkitDataTransferItems(dataTransferItems) {
  function traverseFileTreePromise(item, path='') {
    return new Promise( resolve => {
      if (item.isFile) {
        item.file(file => {
          file.filepath = path + file.name //save full path
          files.push(file)
          resolve(file)
        })
      } else if (item.isDirectory) {
        let dirReader = item.createReader()
        dirReader.readEntries(entries => {
          let entriesPromises = []
          for (let entr of entries)
            entriesPromises.push(traverseFileTreePromise(entr, path + item.name + "/"))
          resolve(Promise.all(entriesPromises))
        })
      }
    })
  }

  let files = []
  return new Promise((resolve, reject) => {
    let entriesPromises = []
    for (let it of dataTransferItems)
      entriesPromises.push(traverseFileTreePromise(it.webkitGetAsEntry()))
    Promise.all(entriesPromises)
      .then(entries => {
        //console.log(entries)
        resolve(files)
      })
  })
}
Run Code Online (Sandbox Code Playgroud)

用法:

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

  var items = event.dataTransfer.items;
  getFilesFromWebkitDataTransferItems(items)
    .then(files => {
      ...
    })
}, false);
Run Code Online (Sandbox Code Playgroud)

npm包

https://www.npmjs.com/package/datatransfer-files-promise

用法示例:https: //github.com/grabantot/datatransfer-files-promise/blob/master/index.html

  • 这应该是新接受的答案.它比其他答案更好,因为它在完成后返回一个承诺.但是有一些错误:`function getFilesWebkitDataTransferItems(dataTransfer)`应该是`function getFilesWebkitDataTransferItems(items)`,而`for(entry of entries)`应该是`for(let entr of entries)`. (4认同)
  • 实际上不会获取目录中的所有文件(对于 Chrome,它只会返回目录中的 100 个条目)。Spec 规定需要重复调​​用 `readEntries` 直到它返回一个空数组。 (2认同)