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

时间:2010-08-28 08:33:01

标签: html5 file-upload drag-and-drop

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

我正在搜索一个简单的拖放式解决方案,用于上传整个文件夹树。

10 个答案:

答案 0 :(得分:66)

现在可以了,感谢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/

答案 1 :(得分:13)

在HTML {5}邮件列表的this message中,Ian Hickson说:

  

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

(另见原feature proposal。) 因此可以安全地假设他认为使用拖放操作上传文件夹也超出了范围。显然,由浏览器来处理单个文件。

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

  

此提案必须有两个   检查(如果可行的话):

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

  2.   
  3. 即使省略了accept属性也会过滤。 Mac OS元数据   和Windows缩略图等应该是   省略。所有隐藏文件和   目录应默认为   排除。

  4.   

答案 2 :(得分:9)

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

<input type='file' webkitdirectory >

用于拖放(对于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>

资源:

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

答案 3 :(得分:9)

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

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

使用承诺(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);
  }
}

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

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

文档:

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

  

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

但公平地说,MDN文档可以在其他部分中使这一点更加清楚。 readEntries()文档只是指出:

  

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

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

  

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

可以说,API也可以更加直观,但是如文档所述:它是一项非标准/实验性功能,不在标准轨道上,并且不能指望所有浏览器都可以使用。

相关:

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

答案 4 :(得分: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。

答案 5 :(得分: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)
      })
  })
}

用法:

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

  var items = event.dataTransfer.items;
  getFilesFromWebkitDataTransferItems(items)
    .then(files => {
      ...
    })
}, false);

npm package

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

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

答案 6 :(得分:2)

HTML5规范并未说明在选择要上传的文件夹时,浏览器应该递归上传所有包含的文件。

实际上,在Chrome / Chromium中,您可以上传文件夹,但是当您这样做时,它只会上传一个无意义的4KB文件,该文件代表该目录。某些服务器端应用程序(如Alfresco)可以检测到此情况,并警告用户无法上载文件夹:

The following cannot be uploaded because they are either folders or are zero bytes in size: undefined

答案 7 :(得分:2)

这是如何使用file and directory entries API的完整示例:

var dropzone = document.getElementById("dropzone");
var listing = document.getElementById("listing");

function scanAndLogFiles(item, container) {
  var elem = document.createElement("li");
  elem.innerHTML = item.name;
  container.appendChild(elem);

  if (item.isDirectory) {
    var directoryReader = item.createReader();
    var directoryContainer = document.createElement("ul");
    container.appendChild(directoryContainer);

    directoryReader.readEntries(function(entries) {
      entries.forEach(function(entry) {
        scanAndLogFiles(entry, directoryContainer);
      });
    });
  }
}

dropzone.addEventListener(
  "dragover",
  function(event) {
    event.preventDefault();
  },
  false
);

dropzone.addEventListener(
  "drop",
  function(event) {
    var items = event.dataTransfer.items;

    event.preventDefault();
    listing.innerHTML = "";

    for (var i = 0; i < items.length; i++) {
      var item = items[i].webkitGetAsEntry();

      if (item) {
        scanAndLogFiles(item, listing);
      }
    }
  },
  false
);
body {
  font: 14px "Arial", sans-serif;
}

#dropzone {
  text-align: center;
  width: 300px;
  height: 100px;
  margin: 10px;
  padding: 10px;
  border: 4px dashed red;
  border-radius: 10px;
}

#boxtitle {
  display: table-cell;
  vertical-align: middle;
  text-align: center;
  color: black;
  font: bold 2em "Arial", sans-serif;
  width: 300px;
  height: 100px;
}
<p>Drag files and/or directories to the box below!</p>

<div id="dropzone">
  <div id="boxtitle">
    Drop Files Here
  </div>
</div>

<h2>Directory tree:</h2>

<ul id="listing"></ul>

Chrome 13 +,Firefox 50+和Edge支持

webkitGetAsEntry

来源:https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry

答案 8 :(得分:1)

  

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

只有Chrome支持此功能。它没有任何牵引力,很可能被删除。

参考:https://developer.mozilla.org/en/docs/Web/API/DirectoryReader#readEntries

答案 9 :(得分:1)

最近偶然发现需要在我的两个项目中实现此功能,因此我创建了一堆实用程序函数来帮助实现此目的。

一个创建一个表示所有文件夹,文件及其之间关系的数据结构,就像?

{
  folders: [
    {
      name: string,
      folders: Array,
      files: Array
    },
    /* ... */
  ],
  files: Array
}

另一个仅返回所有文件的数组(在所有文件夹和子文件夹中)。

以下是该软件包的链接:https://www.npmjs.com/package/file-system-utils