Tôi chưa thấy bất kỳ ví dụ nào làm điều này. Điều này không được phép trong thông số API?
Tôi đang tìm kiếm một giải pháp kéo thả dễ dàng để tải lên toàn bộ cây thư mục ảnh.
Tôi chưa thấy bất kỳ ví dụ nào làm điều này. Điều này không được phép trong thông số API?
Tôi đang tìm kiếm một giải pháp kéo thả dễ dàng để tải lên toàn bộ cây thư mục ảnh.
Câu trả lời:
Bây giờ có thể, nhờ 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);
Thông tin thêm: https://protonet.info/blog/html5-experiment-drag-drop-of-folders/
readEntries
sẽ không trả lại tất cả các yêu cầu trong một thư mục. Dựa vào liên kết lỗi mà bạn cung cấp, tôi đã viết lên một câu trả lời hoàn chỉnh: stackoverflow.com/a/53058574/885922
Thật không may, không có câu trả lời hiện có nào là hoàn toàn chính xác vì readEntries
sẽ không nhất thiết trả về TẤT CẢ các mục nhập (tệp hoặc thư mục) cho một thư mục nhất định. Đây là một phần của đặc tả API (xem phần Tài liệu bên dưới).
Để thực sự nhận được tất cả các tệp, chúng tôi sẽ cần gọi readEntries
nhiều lần (đối với từng thư mục mà chúng tôi gặp phải) cho đến khi nó trả về một mảng trống. Nếu không, chúng tôi sẽ bỏ lỡ một số tệp / thư mục con trong một thư mục, ví dụ như trong Chrome, readEntries
sẽ chỉ trả về tối đa 100 mục nhập cùng một lúc.
Sử dụng Promises ( await
/ async
) để chứng minh rõ ràng hơn cách sử dụng chính xác readEntries
(vì nó không đồng bộ) và tìm kiếm theo chiều rộng (BFS) để duyệt qua cấu trúc thư mục:
// 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);
}
}
Toàn bộ ví dụ làm việc trên Codepen: https://codepen.io/anon/pen/gBJrOP
FWIW Tôi chỉ chọn điều này vì tôi không lấy lại được tất cả các tệp mà tôi mong đợi trong thư mục chứa 40.000 tệp (nhiều thư mục chứa hơn 100 tệp / thư mục con) khi sử dụng câu trả lời được chấp nhận.
Tài liệu:
Hành vi này được ghi lại trong FileSystemDirectoryReader . Đoạn trích có nhấn mạnh thêm:
readEntries ()
Trả về một mảng có chứa một số mục nhập của thư mục . Mỗi mục trong mảng là một đối tượng dựa trên FileSystemEntry — thường là FileSystemFileEntry hoặc FileSystemDirectoryEntry.
Nhưng công bằng mà nói, tài liệu MDN có thể làm rõ hơn điều này trong các phần khác. Tài liệu readEntries () chỉ ghi chú:
Phương thức readEntries () truy xuất các mục nhập thư mục trong thư mục đang được đọc và phân phối chúng trong một mảng tới hàm gọi lại được cung cấp
Và đề cập / gợi ý duy nhất rằng cần có nhiều cuộc gọi nằm trong mô tả của tham số SuccessCallback :
Nếu không còn tệp nào hoặc bạn đã gọi readEntries () trên FileSystemDirectoryReader này, mảng sẽ trống.
Có thể cho rằng API cũng có thể trực quan hơn, nhưng như tài liệu lưu ý: nó là một tính năng không tiêu chuẩn / thử nghiệm, không theo tiêu chuẩn và không thể hoạt động cho tất cả các trình duyệt.
Có liên quan:
readEntries
sẽ trả về tối đa 100 mục nhập cho một thư mục (được xác minh là Chrome 64).readEntries
khá tốt trong câu trả lời này (mặc dù không có mã).readEntries
theo cách không đồng bộ mà không có BFS. Ông cũng lưu ý rằng Firefox trả về tất cả các mục nhập trong một thư mục (không giống như Chrome) nhưng chúng tôi không thể dựa vào điều này với đặc điểm kỹ thuật.FileSystemFileEntry
thành File
, thông qua file(successCb, failureCb)
phương thức. Nếu bạn cũng cần đường dẫn đầy đủ, bạn nên lấy từ đó fileEntry.fullPath
( file.webkitRelativePath
sẽ chỉ là tên).
Hàm này sẽ cung cấp cho bạn một lời hứa cho mảng tất cả các tệp bị loại bỏ, như <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);
https://www.npmjs.com/package/datatransfer-files-promise
ví dụ sử dụng: https://github.com/grabantot/datatransfer-files-promise/blob/master/index.html
function getFilesWebkitDataTransferItems(dataTransfer)
nên function getFilesWebkitDataTransferItems(items)
và for (entr of entries)
nên làm for (let entr of entries)
.
readEntries
nhiều lần cho đến khi nó trả về một mảng trống.
Trong thông báo gửi đến danh sách gửi thư HTML 5 này, Ian Hickson nói:
HTML5 hiện phải tải lên nhiều tệp cùng một lúc. Trình duyệt có thể cho phép người dùng chọn nhiều tệp cùng một lúc, bao gồm trên nhiều thư mục; đó là một chút ngoài phạm vi của thông số kỹ thuật.
(Cũng xem đề xuất tính năng ban đầu .) Vì vậy, có thể an toàn khi cho rằng anh ấy cân nhắc việc tải lên các thư mục bằng cách sử dụng kéo và thả cũng nằm ngoài phạm vi. Rõ ràng là tùy thuộc vào trình duyệt để phân phát các tệp riêng lẻ.
Việc tải lên các thư mục cũng sẽ gặp một số khó khăn khác, như được mô tả bởi Lars Gunther :
Đề xuất […] này phải có hai lần kiểm tra (nếu nó có thể thực hiện được):
Kích thước tối đa, để ngăn ai đó tải lên một thư mục đầy đủ gồm hàng trăm hình ảnh thô không nén ...
Lọc ngay cả khi thuộc tính chấp nhận bị bỏ qua. Siêu dữ liệu Mac OS và hình thu nhỏ Windows, v.v. nên được bỏ qua. Tất cả các tệp và thư mục ẩn sẽ được mặc định loại trừ.
Giờ đây, bạn có thể tải lên các thư mục bằng cả thao tác kéo thả và nhập liệu.
<input type='file' webkitdirectory >
và kéo và thả (Đối với trình duyệt webkit).
Xử lý các thư mục kéo và thả.
<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>
Tài nguyên:
http://updates.html5rocks.com/2012/07/Drag-and-drop-a-folder-onto-Chrome-now-available
Firefox hiện hỗ trợ tải lên thư mục, kể từ ngày 15 tháng 11 năm 2016, trong v50.0: https://developer.mozilla.org/en-US/Firefox/Releases/50#Files_and_directories
Bạn có thể kéo và thả các thư mục vào Firefox hoặc bạn có thể duyệt và chọn một thư mục cục bộ để tải lên. Nó cũng hỗ trợ các thư mục được lồng trong các thư mục con.
Điều đó có nghĩa là bây giờ bạn có thể sử dụng Chrome, Firefox, Edge hoặc Opera để tải lên các thư mục. Bạn không thể sử dụng Safari hoặc Internet Explorer hiện tại.
Dưới đây là một ví dụ đầy đủ về cách sử dụng API mục nhập tệp và thư mục :
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
được hỗ trợ bởi Chrome 13+, Firefox 50+ và Edge.
Nguồn: https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry
HTML5 có cho phép kéo-thả tải lên các thư mục hoặc một cây thư mục không?
Chỉ Chrome mới hỗ trợ tính năng này. Nó đã không có bất kỳ lực kéo nào và có khả năng bị loại bỏ.
Tham khảo: https://developer.mozilla.org/en/docs/Web/API/DirectoryReader#readEntries
readEntries
không thể được gọi nếu một cuộc gọi khác readEntries
vẫn đang được chạy. Thiết kế DirectoryReader API không phải là tốt nhất
CẬP NHẬT: Kể từ năm 2012 đã có nhiều thay đổi, thay vào đó hãy xem câu trả lời ở trên. Tôi để câu trả lời này ở đây vì lợi ích của khảo cổ học.
Thông số HTML5 KHÔNG nói rằng khi chọn một thư mục để tải lên, trình duyệt phải tải lên một cách đệ quy tất cả các tệp chứa trong đó.
Trên thực tế, trong Chrome / Chromium, bạn có thể tải lên một thư mục, nhưng khi bạn làm điều đó, nó chỉ tải lên một tệp 4KB vô nghĩa, đại diện cho thư mục. Một số ứng dụng phía máy chủ như Alfresco có thể phát hiện điều này và cảnh báo người dùng rằng không thể tải lên các thư mục:
Gần đây, tình cờ nhận thấy nhu cầu thực hiện điều này trong hai dự án của tôi, vì vậy tôi đã tạo một loạt các chức năng tiện ích để trợ giúp việc này.
Người ta tạo ra một cấu trúc dữ liệu đại diện cho tất cả các thư mục, tệp và mối quan hệ giữa chúng, giống như vậy
{
folders: [
{
name: string,
folders: Array,
files: Array
},
/* ... */
],
files: Array
}
Trong khi cái kia chỉ trả về một Mảng của tất cả các tệp (trong tất cả các thư mục và thư mục con).
Đây là liên kết đến gói: https://www.npmjs.com/package/file-system-utils
input type=file
: stackoverflow.com/questions/9518335/…