web-dev-qa-db-ja.com

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

これを行う例は見たことがありません。これはAPI仕様で許可されていませんか?

写真のフォルダツリー全体をアップロードするための簡単なドラッグアンドドロップソリューションを探しています。

67
michael

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/

66

残念ながらreadEntriesは必ずしも[〜#〜] all [〜#〜]の(ファイルまたはディレクトリ)エントリを返さないため、既存の回答は完全に正しいものではありません。指定されたディレクトリ。これはAPI仕様の一部です(以下のドキュメントのセクションを参照)。

実際にファイルを取得allするには、空の配列を返すまで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の正しい使用法を非常によく説明しています answer (コードはありませんが)。
  • PabloBarríaUrendaの答え BFSなしで非同期的にreadEntriesを正しく呼び出します。また、Firefoxは(Chromeとは異なり)ディレクトリ内のすべてのエントリを返しますが、仕様を考えるとこれに依存することはできません。
17
xlm

このメッセージ でHTML 5メーリングリストへIan Hickson氏:

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

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

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

この[…]提案mustには2つのチェックがあります(それが実行可能な場合)。

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

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

13
Marcel Korpel

ドラッグアンドドロップと入力の両方でディレクトリをアップロードできるようになりました。

<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

9
Konga Raju

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

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

つまり、Chrome、Firefox、Edge、またはOperaを使用してフォルダーをアップロードできるようになりました。現在、SafariまたはInternet Explorerは使用できません。

8
Dan Roberts

この関数は、<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

8
grabantot

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

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

The following cannot be uploaded because they are either folders or are zero bytes in size: undefined

2
Nicolas Raoul

ファイルおよびディレクトリエントリ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

2
Paolo Moretti

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

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

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

1
basarat