HTML5는 폴더 또는 폴더 트리의 드래그 드롭 업로드를 허용합니까?


82

나는 이것을하는 어떤 예도 보지 못했습니다. API 사양에서 허용되지 않나요?

사진의 전체 폴더 트리를 업로드하기위한 간편한 드래그 드롭 솔루션을 찾고 있습니다.



답변:


81

이제 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/


9
2 년이 지난 후에도 IE와 Firefox는이를 구현할 의사가없는 것 같습니다.
Nicolas Raoul 2014

8
이제 Firefox의 경우 : stackoverflow.com/a/33431704/195216 드래그 앤 드롭 및 크롬 및 파이어 폭스의 대화 상자를 통해 폴더 업로드를 보여줍니다!
dforce

2
Edge도이를 지원합니다.
ZachB

7
중요 경고 :이 답변의 코드는 주어진 디렉토리에서 100 개의 파일로 제한됩니다. 여기를보십시오 : bugs.chromium.org/p/chromium/issues/detail?id=514087
johnozbay

4
@johnozbay 더 많은 사람들이 중요한 경고를 받아 들인 것은 유감이며 사양 readEntries에서 디렉토리의 전체를 모두 반환하지 않는다고 말하고 있기 때문에 반드시 Chromium 문제 는 아닙니다. 제공된 버그 링크를 기반으로 완전한 답변을 작성했습니다. stackoverflow.com/a/53058574/885922
xlm

49

불행히도 주어진 디렉토리에 대한 모든 (파일 또는 디렉토리) 항목을 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로 확인 됨).
  • XanreadEntries 은이 답변 에서 코드가 없지만 올바른 사용법을 설명합니다 .
  • Pablo Barría Urenda의 답변readEntriesBFS없이 비동기 방식으로 올바르게 호출 됩니다. 그는 또한 Firefox가 디렉토리의 모든 항목을 반환하지만 (Chrome과 달리) 사양이 주어지면이를 신뢰할 수 없다고 지적합니다.

4
외쳐 주셔서 감사합니다. SOF는 당신처럼 더 환상적인 멤버가 필요합니다! ✌🏻
johnozbay

6
@johnozbay는 많은 사용자가이 작지만 중요한 사실을 간과하고있는 것 같다는 점에 감사드립니다. 사양 / API 및이 엣지 케이스 (디렉토리에있는 100 개 이상의 파일)는 그럴 가능성이 없습니다. 기대했던 모든 파일을 되찾지 못했을 때만 깨달았습니다. 귀하의 의견은 답변이어야합니다.
xlm

파일 크기를 얻는 방법?
Madeo

모든 관련 메타 데이터 (크기, lastModified, MIME 유형)를 가져 오려면 메서드 를 통해 모두 FileSystemFileEntry를 로 변환해야합니다 . 전체 경로도 필요하면 ( 이름이 될 것 입니다)에서 가져와야합니다. Filefile(successCb, failureCb)fileEntry.fullPathfile.webkitRelativePath
Iskren Ivov Chernev

이것이 최선의 대답처럼 보이지만 Chromium 86에서는 작동하지 않습니다. Firefox에서는 제대로 작동하는 것 같습니다. Chromium에서는 파일이 포함 된 선택 항목을 업로드하지만 readEntriesPromise ()가 빈 배열을 반환하기 때문에 디렉토리에 대해 아무것도 업로드되지 않습니다.
happybeing 2011

15

이 함수는 다음과 같이 드롭 된 모든 파일의 배열에 대한 약속을 제공합니다 <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


4
이것은 새로 받아 들여진 대답이어야합니다. 완료되면 약속을 반환하기 때문에 다른 답변보다 낫습니다. 하지만 몇 가지 실수가있었습니다. function getFilesWebkitDataTransferItems(dataTransfer)이어야 function getFilesWebkitDataTransferItems(items)하고 for (entr of entries)이어야합니다 for (let entr of entries).
RoccoB

1
실제로 디렉터리에있는 모든 파일을 가져 오지는 않습니다 (Chrome의 경우 디렉터리에 100 개의 항목 만 반환 됨). Spec은 readEntries빈 배열을 반환 할 때까지 반복적 으로 호출 할 필요가 있다고 규정합니다 .
xlm

@xlm 업데이트 된 npm 패키지. 이제 100 개 이상의 항목을 처리합니다.
grabantot

매우 유용합니다! 솔루션에 감사드립니다. 지금까지 이것은 가장 정확하고 깨끗한 것입니다. 이것은 새로 받아 들여진 대답이어야합니다. 동의합니다.
싯다 다 초 두리

13

에서 이 메시지 HTML 5 메일 링리스트 이안 힉슨은 말합니다 :

HTML5는 이제 한 번에 많은 파일을 업로드해야합니다. 브라우저를 통해 사용자는 여러 디렉토리를 포함하여 한 번에 여러 파일을 선택할 수 있습니다. 사양 범위를 약간 벗어났습니다.

(또한 원래 기능 제안을 참조하십시오 .) 따라서 그가 끌어서 놓기를 사용하여 폴더를 업로드하는 것도 범위를 벗어난다고 가정하는 것이 안전합니다. 분명히 개별 파일을 제공하는 것은 브라우저에 달려 있습니다.

Lars Gunther가 설명한 것처럼 폴더를 업로드하는 데는 다른 어려움이 있습니다 .

이 […] 제안 에는 두 가지 확인 사항있어야 합니다 (가능한 경우).

  1. 압축되지 않은 원본 이미지 수백 개의 전체 디렉토리를 누군가가 업로드하지 못하도록하는 최대 크기 ...

  2. accept 속성이 생략 된 경우에도 필터링. Mac OS 메타 데이터 및 Windows 썸네일 등은 생략해야합니다. 모든 숨김 파일 및 디렉터리는 기본적으로 제외되어야합니다.


흠, 저는 2 번 점에 동의합니다.하지만 웹 개발자가 숨겨진 파일의 업로드를 활성화할지 여부를 결정할 수있는 방법이있는 한-숨겨진 파일이 작동 할 가능성이 항상 있기 때문입니다. 업로드 된 폴더의 사용. 특히 폴더가 가득 찬 경우에는 최종 컷 파일처럼 여러 부분으로 분할 된 문서가있을 수 있습니다.
Charles John Thompson III

범위를 벗어난 것에 동의하지 않음 : 이것은 많은 사람들이하고 싶어하는 일에 대한 비 호환성의 원인이므로 명시해야합니다.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

10

이제 끌어서 놓기 및 입력으로 디렉토리를 업로드 할 수 있습니다.

<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


1
압축 폴더를 사용하지 않고 다운로드 할 때도 똑같이 할 수 있습니까?
user2284570 aug

8

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를 사용할 수 없습니다.


3

다음은 파일 및 디렉토리 항목 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


1
잘 작동합니다. 뷰에 이식 jsfiddle.net/KimNyholm/xua9kLny
김 니홈

1

HTML5는 폴더 또는 폴더 트리의 드래그 드롭 업로드를 허용합니까?

이 기능은 Chrome에서만 지원합니다. 견인력이 없어 제거 될 가능성이 있습니다.

참고 : https://developer.mozilla.org/en/docs/Web/API/DirectoryReader#readEntries


와. 해당 링크의 W3C Note에서 말하면 실제로 계속되지 않습니다. 견인력을 얻지 못했다는 가정의 근거는 무엇입니까?
bebbi

@bebbi 다른 브라우저 벤더가 구현하지
basarat

1
@ PabloBarríaUrenda 의견이 사실이 아닙니다. 그의 문제는 그의 질문을 참조 할 가능성이 높습니다. stackoverflow.com/questions/51850469/… 그가 해결 / 실현 한 readEntries다른 호출 readEntries이 아직 실행중인 경우 호출 할 수 없습니다 . DirectoryReader API 디자인은 최고가 아닙니다
xlm

@xlm 예, 실제로 당신이 맞습니다. 나는이 문제에 당혹스러워하는 동안 이것을 게시했지만 결국 해결했습니다 (그리고이 댓글을 잊어 버렸습니다). 이제 헷갈리는 댓글을 삭제했습니다.
Pablo Barría Urenda 2018

1

업데이트 : 2012 년 이후로 많은 것이 변경되었으므로 대신 위의 답변을 참조하십시오. 고고학을 위해이 답변을 여기에 남겨 둡니다.

HTML5 사양에는 업로드 할 폴더를 선택할 때 브라우저가 포함 된 모든 파일을 재귀 적으로 업로드해야한다고 명시되어 있지 않습니다.

실제로 Chrome / Chromium에서는 폴더를 업로드 할 수 있지만 그렇게하면 디렉토리를 나타내는 의미없는 4KB 파일 만 업로드됩니다. Alfresco 와 같은 일부 서버 측 응용 프로그램은 이를 감지 할 수 있으며 사용자에게 폴더를 업로드 할 수 없음을 경고합니다.

다음은 폴더이거나 크기가 0 바이트이므로 업로드 할 수 없습니다. undefined


@MoB : 아마도 그것은 일종의 포인터 일 것입니다. 그러나 실제 파일이 클라이언트 시스템에 있기 때문에 서버 시스템은 물론이 포인터로 아무것도 할 수 없습니다.
Nicolas Raoul 2012 년

1

최근에 내 프로젝트 중 두 개에서 이것을 구현해야 할 필요성을 발견했기 때문에 이것을 돕기 위해 많은 유틸리티 함수를 만들었습니다.

하나는 모든 폴더, 파일 및 그 사이의 관계를 나타내는 데이터 구조를 만듭니다.

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

다른 하나는 모든 파일 (모든 폴더 및 하위 폴더)의 배열을 반환합니다.

다음은 패키지 링크입니다. https://www.npmjs.com/package/file-system-utils

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.