我还没有看到任何这样做的例子。这在 API 规范中是不允许的吗?
我正在寻找一种简单的拖放解决方案来上传整个照片文件夹树。
我还没有看到任何这样做的例子。这在 API 规范中是不允许的吗?
我正在寻找一种简单的拖放解决方案来上传整个照片文件夹树。
现在可以了,多亏了 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/
不幸的是,现有的答案都不是完全正确的,因为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);
}
}
Codepen 上的完整工作示例:https ://codepen.io/anon/pen/gBJrOP
FWIW 我只是选择了这个,因为在使用接受的答案时,我没有在包含 40,000 个文件的目录(许多目录包含超过 100 个文件/子目录)中取回我期望的所有文件。
文档:
此行为记录在FileSystemDirectoryReader 中。节选并强调:
readEntries()
返回一个包含一定数量 目录条目的数组。数组中的每一项都是基于 FileSystemEntry 的对象——通常是 FileSystemFileEntry 或 FileSystemDirectoryEntry。
但公平地说,MDN 文档可以在其他部分更清楚地说明这一点。该readEntries()文件只是指出:
readEntries()方法检索正在读取的目录中的目录条目,并将它们以数组形式传递给提供的回调函数
唯一需要多次调用的提及/提示是在successCallback参数的描述中:
如果没有剩余文件,或者您已经在此 FileSystemDirectoryReader 上调用了 readEntries(),则该数组为空。
可以说 API 也可以更直观,但正如文档所指出的那样:它是一个非标准/实验性功能,不在标准轨道上,并且不能期望适用于所有浏览器。
有关的:
readEntries
一个目录最多返回 100 个条目(验证为 Chrome 64)。readEntries
在这个答案中很好地解释了 的正确用法(尽管没有代码)。readEntries
以异步方式正确调用,无需 BFS。他还指出,Firefox 会返回目录中的所有条目(与 Chrome 不同),但鉴于规范,我们不能依赖于此。此函数将为您提供所有已删除文件的数组的Promise,例如<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 包:https : //www.npmjs.com/package/datatransfer-files-promise
使用示例:https : //github.com/grabantot/datatransfer-files-promise/blob/master/index.html
在发送给 HTML 5 邮件列表的这条消息中,Ian Hickson 说:
HTML5 现在必须一次上传许多文件。浏览器可以允许用户一次选择多个文件,包括跨多个目录;这有点超出规范的范围。
(另请参阅原始功能提案。)因此可以安全地假设他考虑使用拖放上传文件夹也超出范围。显然,由浏览器来提供单个文件。
正如Lars Gunther所描述的,上传文件夹也会遇到一些其他困难:
这个 [...] 提案必须有两个检查(如果它完全可行):
最大尺寸,以阻止某人上传包含数百个未压缩原始图像的完整目录...
即使省略了接受属性,也进行过滤。应省略 Mac OS 元数据和 Windows 缩略图等。默认情况下应排除所有隐藏文件和目录。
现在您可以通过拖放和输入上传目录。
<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