Подтвердить что ты не робот

Поддерживает ли HTML5 перетаскивание загрузки папок или дерева папок?

Я не видел никаких примеров, которые делают это. Разве это не разрешено в спецификации API?

Я ищу простое перетаскиваемое решение для загрузки всего дерева папок с фотографиями.

4b9b3361

Ответ 1

Теперь это возможно, благодаря 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/

Ответ 2

К сожалению, ни один из существующих ответов не является полностью правильным, потому что readEntries не обязательно вернет ALL записи (файл или каталог) для данного каталога. Это часть спецификации 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 Я только поднял это, потому что я не возвращал все файлы, которые я ожидал в каталоге, содержащем 40000 файлов (многие каталоги, содержащие более 100 файлов/подкаталогов) при использовании принятого ответа.

Документация:

Это поведение описано в FileSystemDirectoryReader. Выдержка с добавлением акцента:

readEntries()
Возвращает массив, содержащий некоторое количество записи каталога. Каждый элемент в массиве является объектом, основанным на FileSystemEntry - обычно либо FileSystemFileEntry, либо FileSystemDirectoryEntry.

Но, честно говоря, документация MDN могла бы прояснить это в других разделах. В документации readEntries() просто указано:

Метод readEntries() извлекает записи каталога в читаемом каталоге и доставляет их в массиве в функцию обратного вызова

И единственное упоминание/подсказка о необходимости нескольких вызовов - в описании параметра successCallback :

Если не осталось файлов или вы уже вызвали readEntries() на этот FileSystemDirectoryReader, массив пуст.

Можно утверждать, что API также может быть более интуитивно понятным, но, как отмечается в документации: это нестандартная/экспериментальная функция, не соответствующая стандартам, и ее нельзя ожидать для всех браузеров.

Связанный:

  • johnozbay комментирует, что в Chrome readEntries вернет не более 100 записей для каталога (проверено как Chrome 64).
  • Ксан объясняет правильное использование readEntries в этом ответе (хотя и без кода).
  • Ответ пабло Баррии Уренды правильно вызывает readEntries асинхронно без BFS. Он также отмечает, что Firefox возвращает все записи в каталоге (в отличие от Chrome), но мы не можем полагаться на это, учитывая спецификацию.

Ответ 3

В это сообщение в список рассылки HTML 5 Ян Хиксон говорит:

HTML5 теперь загружает много файлов на один раз. Браузеры могут выберите сразу несколько файлов, включая через несколько каталогов; который бит из объема спецификации.

(Также см. первоначальное предложение ).) Поэтому можно с уверенностью предположить, что он считает, что загрузка папок с использованием перетаскивания также выходит за рамки. По-видимому, это до браузера, чтобы обслуживать отдельные файлы.

Загрузка папок также будет иметь некоторые другие трудности, как описано Lars Gunther:

В этом [...] предложении должно быть два проверяет (если это вообще возможно):

  • Максимальный размер, чтобы кто-то не мог загрузить полный каталог из нескольких сто несжатых исходных изображений...

  • Фильтрация, даже если атрибут accept опущен. Метаданные для Mac OS и миниатюры Windows и т.д. должны быть опущено. Все скрытые файлы и каталоги должны по умолчанию быть исключены.

Ответ 4

Теперь вы можете загружать каталоги с помощью перетаскивания и ввода.

<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

Ответ 5

Теперь Firefox поддерживает загрузку папок по состоянию на 15 ноября 2016 года в версии 50.0: https://developer.mozilla.org/en-US/Firefox/Releases/50#Files_and_directories

Вы можете перетаскивать папки в Firefox или просматривать и выбирать локальную папку для загрузки. Он также поддерживает папки, вложенные в подпапки.

Это означает, что теперь вы можете использовать Chrome, Firefox, Edge или Opera для загрузки папок. Вы не можете использовать Safari или Internet Explorer в настоящее время.

Ответ 6

Эта функция даст вам обещание для массива всех удаленных файлов, например, <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

Ответ 7

Спецификация HTML5 НЕ говорит, что при выборе папки для загрузки браузер должен рекурсивно загружать все содержащиеся файлы.

На самом деле, в Chrome/Chromium вы можете загрузить папку, но когда вы это делаете, она просто загружает бессмысленный файл размером 4 КБ, который представляет каталог. Некоторые серверные приложения, такие как Alfresco, могут обнаружить это и предупредить пользователя о невозможности загрузки папок:

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

Ответ 8

Вот полный пример использования 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>

Ответ 9

Поддерживает ли HTML5 перетаскивание загрузки папок или дерева папок?

Только Chrome поддерживает эту функцию. Он не имеет никакого сцепления и, вероятно, будет удален.

Ссылка: https://developer.mozilla.org/en/docs/Web/API/DirectoryReader#readEntries