Html HTML5 是否允许拖放上传文件夹或文件夹树?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3590058/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Does HTML5 allow drag-drop upload of folders or a folder tree?
提问by michael
I haven't seen any examples that do this. Is this not allowed in the API spec?
我还没有看到任何这样做的例子。这在 API 规范中是不允许的吗?
I am searching for an easy drag-drop solution for uploading an entire folder tree of photos.
我正在寻找一种简单的拖放解决方案来上传整个照片文件夹树。
采纳答案by Christopher Blum
It's now possible, thanks to Chrome >= 21.
由于 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);
More info: https://protonet.info/blog/html5-experiment-drag-drop-of-folders/
更多信息:https: //protonet.info/blog/html5-experiment-drag-drop-of-folders/
回答by xlm
Unfortunately none of the existing answers are completely correct because readEntries
will not necessarily return ALLthe (file or directory) entries for a given directory. This is part of the API specification (see Documentation section below).
不幸的是,现有的答案都不是完全正确的,因为readEntries
不一定会返回给定目录的所有(文件或目录)条目。这是 API 规范的一部分(请参阅下面的文档部分)。
To actually get allthe files, we'll need to call readEntries
repeatedly (for each directory we encounter) until it returns an empty array. If we don't, we will miss some files/sub-directories in a directory e.g. in Chrome, readEntries
will only return at most 100 entries at a time.
要实际获取所有文件,我们需要readEntries
重复调用(针对我们遇到的每个目录),直到它返回一个空数组。如果我们不这样做,我们将错过目录中的一些文件/子目录,例如在 Chrome 中,readEntries
一次最多只能返回 100 个条目。
Using Promises (await
/ async
) to more clearly demonstrate the correct usage of readEntries
(since it's asynchronous), and breadth-first search (BFS) to traverse the directory structure:
使用 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);
}
}
Complete working example on Codepen: https://codepen.io/anon/pen/gBJrOP
Codepen 上的完整工作示例:https://codepen.io/anon/pen/gBJrOP
FWIW I only picked this up because I wasn't getting back all the files I expected in a directory containing 40,000 files (many directories containing well over 100 files/sub-directories) when using the accepted answer.
FWIW 我只是选择了这个,因为在使用接受的答案时,我没有在包含 40,000 个文件的目录(许多目录包含超过 100 个文件/子目录)中取回我期望的所有文件。
Documentation:
文档:
This behaviour is documented in FileSystemDirectoryReader. Excerpt with emphasis added:
此行为记录在FileSystemDirectoryReader 中。摘录并强调:
readEntries()
Returns a an array containing some number of thedirectory's entries. Each item in the array is an object based on FileSystemEntry—typically either FileSystemFileEntry or FileSystemDirectoryEntry.
readEntries()
返回一个包含一定数量目录条目的数组。数组中的每一项都是基于 FileSystemEntry 的对象——通常是 FileSystemFileEntry 或 FileSystemDirectoryEntry。
But to be fair, the MDN documentation could make this clearer in other sections. The readEntries()documentation simply notes:
但公平地说,MDN 文档可以在其他部分更清楚地说明这一点。该readEntries()文件只是指出:
readEntries()method retrieves the directory entries within the directory being read and delivers them in an array to the provided callback function
readEntries()方法检索正在读取的目录中的目录条目,并将它们以数组形式传递给提供的回调函数
And the only mention/hint that multiple calls are needed is in the description of successCallbackparameter:
唯一需要多次调用的提及/提示是在successCallback参数的描述中:
If there are no files left, or you've already called readEntries() on this FileSystemDirectoryReader, the array is empty.
如果没有剩余文件,或者您已经在此 FileSystemDirectoryReader 上调用了 readEntries(),则该数组为空。
Arguably the API could be more intuitive as well, but as the documentation notes: it is a non-standard/experimental feature, not on a standards track, and can't be expected to work for all browsers.
可以说 API 也可以更直观,但正如文档所指出的那样:它是一个非标准/实验性功能,不在标准轨道上,并且不能期望适用于所有浏览器。
Related:
有关的:
- johnozbay commentsthat on Chrome,
readEntries
will return at most 100 entries for a directory (verified as Chrome 64). - Xanexplains the correct usage of
readEntries
quite well in this answer(albeit without code). - Pablo Barría Urenda's answercorrectly calls
readEntries
in a asynchronous manner without BFS. He also notes that Firefox returns all the entries in a directory (unlike Chrome) but we can't rely on this given the specification.
- johnozbay 评论说,在 Chrome 上,
readEntries
一个目录最多返回 100 个条目(验证为 Chrome 64)。 - Xan
readEntries
在这个答案中很好地解释了 的正确用法(尽管没有代码)。 - Pablo Barría Urenda 的回答
readEntries
以异步方式正确调用,无需 BFS。他还指出,Firefox 会返回目录中的所有条目(与 Chrome 不同),但鉴于规范,我们不能依赖于此。
回答by Marcel Korpel
In this messageto the HTML 5 mailing list Ian Hickson says:
在给 HTML 5 邮件列表的这条消息中,Ian Hickson 说:
HTML5 now has to upload many files at once. Browsers could allow users to pick multiple files at once, including across multiple directories; that's a bit out of scope of the spec.
HTML5 现在必须一次上传许多文件。浏览器可以允许用户一次选择多个文件,包括跨多个目录;这有点超出规范的范围。
(Also see the original feature proposal.) So it's safe to assume he considers uploading folders using drag-and-drop also out of scope. Apparently it's up to the browser to serve individual files.
(另请参阅原始功能提案。)因此可以安全地假设他考虑使用拖放上传文件夹也超出范围。显然,由浏览器来提供单个文件。
Uploading folders would also have some other difficulties, as described by Lars Gunther:
正如Lars Gunther所描述的,上传文件夹也会遇到一些其他困难:
This […] proposal musthave two checks (if it is doable at all):
Max size, to stop someone from uploading a full directory of several hundred uncompressed raw images...
Filtering even if the accept attribute is omitted. Mac OS metadata and Windows thumbnails, etc should be omitted. All hidden files and directories should default to be excluded.
这个 […] 提案必须有两个检查(如果它完全可行):
最大尺寸,以阻止某人上传包含数百个未压缩原始图像的完整目录...
即使省略了 accept 属性也进行过滤。应省略 Mac OS 元数据和 Windows 缩略图等。应默认排除所有隐藏文件和目录。
回答by grabantot
This function will give you a promise for array of all dropped files, like <input type="file"/>.files
:
此函数将为您提供所有已删除文件的数组的承诺,例如<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)
})
})
}
Usage:
用法:
dropArea.addEventListener("drop", function(event) {
event.preventDefault();
var items = event.dataTransfer.items;
getFilesFromWebkitDataTransferItems(items)
.then(files => {
...
})
}, false);
npm package
npm 包
https://www.npmjs.com/package/datatransfer-files-promise
https://www.npmjs.com/package/datatransfer-files-promise
usage example: https://github.com/grabantot/datatransfer-files-promise/blob/master/index.html
使用示例:https: //github.com/grabantot/datatransfer-files-promise/blob/master/index.html
回答by Konga Raju
Now you can upload directories with both drag and drop and input.
现在您可以通过拖放和输入上传目录。
<input type='file' webkitdirectory >
and for drag and drop(For webkit browsers).
和拖放(对于 webkit 浏览器)。
Handling drag and drop folders.
处理拖放文件夹。
<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>
Resources:
资源:
http://updates.html5rocks.com/2012/07/Drag-and-drop-a-folder-onto-Chrome-now-available
http://updates.html5rocks.com/2012/07/Drag-and-drop-a-folder-onto-Chrome-now-available
回答by Dan Roberts
Firefox now supports folder upload, as of November 15, 2016, in v50.0: https://developer.mozilla.org/en-US/Firefox/Releases/50#Files_and_directories
Firefox 现在支持文件夹上传,截至 2016 年 11 月 15 日,在 v50.0:https: //developer.mozilla.org/en-US/Firefox/Releases/50#Files_and_directories
You can drag and drop folders into Firefox or you can browse and select a local folder to upload. It also supports folders nested in subfolders.
您可以将文件夹拖放到 Firefox 中,也可以浏览并选择要上传的本地文件夹。它还支持嵌套在子文件夹中的文件夹。
That means you can now use either Chrome, Firefox, Edge or Opera to upload folders. You can't use Safari or Internet Explorer at present.
这意味着您现在可以使用 Chrome、Firefox、Edge 或 Opera 上传文件夹。您目前无法使用 Safari 或 Internet Explorer。
回答by Paolo Moretti
Here's a complete example of how to use the file and directory entries API:
以下是如何使用文件和目录条目 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>
webkitGetAsEntry
is supported by Chrome 13+, Firefox 50+ and Edge.
webkitGetAsEntry
Chrome 13+、Firefox 50+ 和 Edge 支持。
Source: https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry
来源:https: //developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry
回答by Pava
Recently stumbled upon the need to implement this in two of my projects so I created a bunch of utility functions to help with this.
最近偶然发现需要在我的两个项目中实现这一点,所以我创建了一堆实用函数来帮助解决这个问题。
One creates a data-structure representing all the folders, files and relationship between them, like so
创建一个数据结构来表示所有文件夹、文件和它们之间的关系,就像这样
{
folders: [
{
name: string,
folders: Array,
files: Array
},
/* ... */
],
files: Array
}
While the other just returns an Array of all the files (in all folders and sub-folders).
而另一个只返回所有文件的数组(在所有文件夹和子文件夹中)。
Here's the link to the package: https://www.npmjs.com/package/file-system-utils
回答by basarat
Does HTML5 allow drag-drop upload of folders or a folder tree?
HTML5 是否允许拖放上传文件夹或文件夹树?
Only Chrome supports this feature. It has failed to have any traction and is likely to be removed.
只有 Chrome 支持此功能。它没有任何牵引力,很可能会被移除。
Ref : https://developer.mozilla.org/en/docs/Web/API/DirectoryReader#readEntries
参考:https: //developer.mozilla.org/en/docs/Web/API/DirectoryReader#readEntries
回答by Nicolas Raoul
UPDATE: Since 2012 a lot has changed, see answers above instead. I leave this answer here for the sake of archeology.
更新:自 2012 年以来发生了很多变化,请参阅上面的答案。为了考古,我把这个答案留在这里。
The HTML5 spec does NOT say that when selecting a folder for upload, the browser should upload all contained files recursively.
HTML5 规范没有说在选择要上传的文件夹时,浏览器应该递归上传所有包含的文件。
Actually, in Chrome/Chromium, you can upload a folder, but when you do it, it just uploads a meaningless 4KB file, which represents the directory. Some servers-side applications like Alfrescocan detect this, and warn the user that folders can not be uploaded:
其实在 Chrome/Chromium 中,你可以上传一个文件夹,但是当你上传时,它只是上传一个无意义的 4KB 文件,代表目录。一些服务器端应用程序如Alfresco可以检测到这一点,并警告用户文件夹无法上传: