나는 이것을하는 어떤 예도 보지 못했습니다. 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 (Broadth-First Search)를 사용하여 디렉터리 구조를 탐색합니다.
// 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는 더 직관적 일 수 있지만 문서에서 알 수 있듯이 표준 트랙이 아닌 비표준 / 실험적 기능이며 모든 브라우저에서 작동 할 것으로 기대할 수 없습니다.
관련 :
- johnozbay 는 Chrome에서
readEntries
디렉토리에 대해 최대 100 개의 항목을 반환 할 것이라고 설명 합니다 (Chrome 64로 확인 됨). - Xan
readEntries
은이 답변 에서 코드가 없지만 올바른 사용법을 설명합니다 . - Pablo Barría Urenda의 답변 은
readEntries
BFS없이 비동기 방식으로 올바르게 호출 됩니다. 그는 또한 Firefox가 디렉토리의 모든 항목을 반환하지만 (Chrome과 달리) 사양이 주어지면이를 신뢰할 수 없다고 지적합니다.
답변
이 함수는 다음과 같이 드롭 된 모든 파일의 배열에 대한 약속을 제공합니다 <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 메일 링리스트 이안 힉슨은 말합니다 :
HTML5는 이제 한 번에 많은 파일을 업로드해야합니다. 브라우저를 통해 사용자는 여러 디렉토리를 포함하여 한 번에 여러 파일을 선택할 수 있습니다. 사양 범위를 약간 벗어났습니다.
(또한 원래 기능 제안을 참조하십시오 .) 따라서 그가 끌어서 놓기를 사용하여 폴더를 업로드하는 것도 범위를 벗어난다고 가정하는 것이 안전합니다. 분명히 개별 파일을 제공하는 것은 브라우저에 달려 있습니다.
Lars Gunther가 설명한 것처럼 폴더를 업로드하는 데는 다른 어려움이 있습니다 .
이 […] 제안 에는 두 가지 확인 사항 이 있어야 합니다 (가능한 경우).
압축되지 않은 원본 이미지 수백 개의 전체 디렉토리를 누군가가 업로드하지 못하도록하는 최대 크기 …
accept 속성이 생략 된 경우에도 필터링. Mac OS 메타 데이터 및 Windows 썸네일 등은 생략해야합니다. 모든 숨김 파일 및 디렉터리는 기본적으로 제외되어야합니다.
답변
이제 끌어서 놓기 및 입력으로 디렉토리를 업로드 할 수 있습니다.
<input type='file' webkitdirectory >
드래그 앤 드롭 (웹킷 브라우저 용).
드래그 앤 드롭 폴더 처리.
<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
답변
Firefox는 이제 v50.0에서 2016 년 11 월 15 일부터 폴더 업로드를 지원합니다. https://developer.mozilla.org/en-US/Firefox/Releases/50#Files_and_directories
Firefox로 폴더를 끌어다 놓거나 업로드 할 로컬 폴더를 찾아서 선택할 수 있습니다. 또한 하위 폴더에 중첩 된 폴더도 지원합니다.
즉, 이제 Chrome, Firefox, Edge 또는 Opera를 사용하여 폴더를 업로드 할 수 있습니다. 현재 Safari 또는 Internet Explorer를 사용할 수 없습니다.
답변
다음은 파일 및 디렉토리 항목 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
Chrome 13 이상, Firefox 50 이상 및 Edge에서 지원됩니다.
출처 : https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry