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はこれを実装する気がないようです。
ニコラスラウル2014

8
さて、Firefoxの場合も:stackoverflow.com/a/33431704/195216ドラッグアンドドロップとChromeとFirefoxのダイアログを介したフォルダのアップロードが表示されます!
dforce 2015年

2
Edgeもこれをサポートしています。
zachB 2016

7
重要な警告:この回答のコードは、特定のディレクトリ内の100ファイルに制限されています。ここを参照してください: bugs.chromium.org/p/chromium/issues/detail
id

4
@johnozbay多くの人があなたの重要な警告を受け取ったのは残念です。仕様ではreadEntries、ディレクトリ内のすべての全体が返されるわけではないため、必ずしもChromiumの問題ではありません。:バグのリンクに基づいて、あなたの提供、私は完全な答えまで書いてきたstackoverflow.com/a/53058574/885922
XLM

49

残念ながら、特定のディレクトリのすべての(ファイルまたはディレクトリ)エントリが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もより直感的である可能性がありますが、ドキュメントに記載されているように、これは非標準/実験的な機能であり、標準トラックではなく、すべてのブラウザーで機能するとは限りません。

関連:

  • johnozbayは、ChromeではreadEntries、ディレクトリに対して最大100のエントリを返すとコメントしています(Chrome 64として確認済み)。
  • Xanは、readEntriesこの回答で非常によく使用法を説明しています(コードはありませんが)。
  • PabloBarríaUrendaの回答readEntries、BFSを使用せずに非同期で正しく呼び出します。彼はまた、Firefoxは(Chromeとは異なり)ディレクトリ内のすべてのエントリを返すが、仕様を考えるとこれに依存することはできないと述べています。

4
叫び声を上げて、このコンテンツを公開してくれてありがとう。SOFにはあなたのようなもっと素晴らしいメンバーが必要です!✌🏻
johnozbay

6
@johnozbayに感謝します。多くのユーザーが、仕様/ APIに関するこの小さいながらも重要な事実を見落としているようであり、このエッジケース(ディレクトリ内の100以上のファイル)はそれほどありそうもないことではありません。期待したすべてのファイルが返されなかったときに初めて気づきました。あなたのコメントは答えであるはずです。
xlm 2018年

ファイルサイズを取得する方法は?
マデオ

関連するすべてのメタデータ(サイズ、lastModified、mimeタイプ)を取得するには、メソッドを介してすべてFileSystemFileEntryをに変換する必要があります。フルパスも必要な場合は、から取得する必要があります(名前だけになります)。Filefile(successCb, failureCb)fileEntry.fullPathfile.webkitRelativePath
Iskren IvovChernev20年

これは最良の答えのようですが、Chromium86では機能しません。Firefoxでは正常に機能するようです。Chromiumでは、ファイルを含む選択範囲がアップロードされますが、readEntriesPromise()が空の配列を返すため、ディレクトリには何もアップロードされません。
幸せな

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 2017

1
ディレクトリ内のすべてのファイルを実際に取得するわけではありません(Chromeの場合、ディレクトリ内の100エントリのみが返されます)。SpecreadEntriesは、空の配列を返すまで繰り返し呼び出す必要があることを規定しています。
XLM

@xlmnpmパッケージを更新しました。現在、100を超えるエントリを処理します。
grabantot 2018年

非常に役立ちます!解決してくれてありがとう。これまでのところ、これは最も正確でクリーンなものです。これは新しく受け入れられた答えであるはずです、私は同意します。
SiddharthaChowdhury20年

13

、このメッセージHTML 5メーリングリストへのIan Hickson氏は述べています:

HTML5は、一度に多くのファイルをアップロードする必要があります。ブラウザを使用すると、ユーザーは複数のディレクトリを含め、一度に複数のファイルを選択できます。それは仕様の範囲から少し外れています。

(元の機能の提案も参照してください。)したがって、ドラッグアンドドロップを使用してフォルダーをアップロードすることも範囲外であると彼が考えていると考えるのは安全です。どうやら、個々のファイルを提供するのはブラウザ次第です。

Lars Guntherが説明しているように、フォルダのアップロードには他にもいくつかの問題があります。

この[…]提案に、2つのチェックが必要です(実行可能かどうか)。

  1. 最大サイズ、誰かが数百の非圧縮生画像の完全なディレクトリをアップロードするのを防ぐために...

  2. accept属性が省略されている場合でもフィルタリング。MacOSのメタデータやWindowsのサムネイルなどは省略してください。すべての隠しファイルとディレクトリは、デフォルトで除外されるはずです。


うーん、私はポイント2に同意します...しかし、隠しファイルのアップロードを有効にするかどうかをWeb開発者が判断する方法がある場合に限ります-隠しファイルが機能する可能性は常にあるためですアップロードされたフォルダの使用。特に、フォルダがドキュメントでいっぱいの場合は、最終的なカットファイルのように複数の部分に分割されます。
チャールズジョントンプソンIII

範囲外に同意しない:これは、多くの人がやりたいことの非互換性の原因であるため、指定する必要があります。
CiroSantilli郝海东冠事病六四事件法轮功2014

10

これで、ドラッグアンドドロップと入力の両方でディレクトリをアップロードできます。

<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


1
圧縮フォルダを使用せずにダウンロードする場合も同じことができますか?
user2284570 2015

8

Firefoxは、2016年11月15日の時点で、v50.0でフォルダーのアップロードをサポートするようになりました:https://developer.mozilla.org/en-US/Firefox/Releases/50#Files_and_directories

フォルダをFirefoxにドラッグアンドドロップするか、アップロードするローカルフォルダを参照して選択することができます。また、サブフォルダーにネストされたフォルダーもサポートします。

つまり、Chrome、Firefox、Edge、Operaのいずれかを使用してフォルダをアップロードできるようになりました。現在、SafariやInternetExplorerはご利用いただけません。


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
キムNyholm

1

HTML5では、フォルダまたはフォルダツリーのドラッグアンドドロップアップロードが可能ですか?

Chromeのみがこの機能をサポートしています。牽引力がなく、削除される可能性があります。

参照:https//developer.mozilla.org/en/docs/Web/API/DirectoryReader#readEntries


ワオ。そのリンクのW3CNoteからわかるように、これは実際には継続されていません。それが牽引力を得ることができなかったという仮定の根拠は何ですか?
bebbi 2016

@bebbi他のブラウザベンダーはそれを実装していません
basarat 2016

1
@PabloBarríaUrendaのコメントは真実ではありません。彼の問題はおそらく彼の質問を参照しています:stackoverflow.com/questions/51850469/…彼が解決/実現したreadEntriesものは、別の呼び出しreadEntriesがまだ実行されている場合は呼び出すことができません。DirectoryReader APIの設計は最適ではありません
xlm 2018年

@xlmはい、確かにあなたは正しいです。私自身がこの問題に戸惑いながら投稿しましたが、最終的には解決しました(そしてこのコメントを忘れました)。紛らわしいコメントを削除しました。
パブロBarríaUrenda

1

更新:2012年以降、多くの変更がありました。代わりに上記の回答を参照してください。私は考古学のためにこの答えをここに残します。

HTML5仕様では、アップロードするフォルダーを選択するときに、ブラウザーは含まれているすべてのファイルを再帰的にアップロードする必要があるとは述べていません。

実際、Chrome / Chromiumではフォルダをアップロードできますが、アップロードすると、ディレクトリを表す意味のない4KBのファイルがアップロードされるだけです。Alfrescoなどの一部のサーバー側アプリケーションはこれを検出し、フォルダーをアップロードできないことをユーザーに警告できます。

以下はフォルダであるか、サイズが0バイトであるため、アップロードできません:undefined


@MoB:多分それは確かにある種のポインターです。ただし、実際のファイルはクライアントマシン上にあるため、サーバーマシンはもちろんこのポインターを使用して何もできません。
ニコラスラウル

1

最近、私の2つのプロジェクトでこれを実装する必要性に遭遇したので、これを支援するためのユーティリティ関数をたくさん作成しました。

1つは、すべてのフォルダー、ファイル、およびそれらの間の関係を表すデータ構造を作成します👇

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

もう1つは、すべてのファイル(すべてのフォルダーとサブフォルダー内)の配列を返すだけです。

パッケージへのリンクは次のとおりです:https//www.npmjs.com/package/file-system-utils

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.