Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Мультизагрузка картинок (https://javascript.ru/forum/misc/72146-multizagruzka-kartinok.html)

LingVist 11.01.2018 20:57

Мультизагрузка картинок
 
Доброго дня всем

Есть форма и инпут типа 'file' с атрибутом 'multiple'. Также имется предпросмотр выбранных картинок.

Когда юзер выбирает сразу несколько изображений, то они все попадают в массив $_FILES и все работает нормально.

Но если юзер выбирает одну картинку, закрывает окно выбора, потом следующую и т.д., то в массив $_FILES попадает последняя. При этом в окне предпросмотра все они показываются нормально.

Такой вопрос - как их объединить, чтобы все работало так, как будто выбираются сразу несколько файлов?

Код предпросмотра
window.onload = function(){
        
    //Check File API support
    if(window.File && window.FileList && window.FileReader){
        var filesInput = document.getElementById("files");
        
        filesInput.addEventListener("change", function(event){
            
            var files = event.target.files; //FileList object
            var output = document.getElementById("result");
            
            for(var i = 0; i< files.length; i++){
                var file = files[i];                
                //Only pics
                if(!file.type.match('image'))
                  continue;
                
                var picReader = new FileReader();
                
                picReader.addEventListener("load",function(event){                    
                    var picFile = event.target;
                    var div = document.createElement("div");
                    div.className = 'img_preview';
                    div.innerHTML = "<img class='thumbnail' src='" + picFile.result + "'" +
                            "title='" + picFile.name + "'/>";                    
                    output.insertBefore(div,null); 
                    
                
                });                
                 //Read the image
                picReader.readAsDataURL(file);
            }                               
           
        });
    }
    else
    {
        console.log("Your browser does not support File API");
    }
}


Спасибо

Nexus 11.01.2018 21:16

Нужно удалять все превью, когда пользователь просто закрывает окно выбора файла, т.к. все выбранные до этого файлы "сбрасываются".
После строки 11:
if(!files.length)
    output.innerHTML='';

LingVist 11.01.2018 21:28

Цитата:

Сообщение от Nexus (Сообщение 474876)
Нужно удалять все превью, когда пользователь просто закрывает окно выбора файла, т.к. все выбранные до этого файлы "сбрасываются".
После строки 11:
if(!files.length)
    output.innerHTML='';

Вставил после 11-й строки, все осталось по-прежнему

рони 11.01.2018 21:31

Nexus,
похоже вопрос в том как отправить ВСЕ картинки из output на сервер?

LingVist 11.01.2018 21:33

Цитата:

Сообщение от рони (Сообщение 474880)
Nexus,
похоже вопрос в том как отправить ВСЕ картинки из output на сервер?

Они и отправляются, но если выбирать кучей. А если по одной добавлять, то отправляется последняя

Nexus 11.01.2018 21:45

<input type="file" multiple id="files" accept="image/*"/>
<div id="result"></div>
<style>.thumbnail{max-width:100px;}</style>
<script>
(function(){
  if(!window.File || !window.FileList || !window.FileReader) 
    return console.log("Your browser does not support File API");

  document.getElementById("files").addEventListener("change", function(event) {
    var files = event.target.files,
        output = document.getElementById("result");

    output.innerHTML = '';
    for(var i = 0; i < files.length; i++) {
      var file = files[i];
      if(!file.type.match('image')) 
        continue;

      var picReader = new FileReader();
      picReader.addEventListener("load", function(event) {
        var picFile = event.target;
        var div = document.createElement("div");
        div.className = 'img_preview';
        div.innerHTML = "<img class='thumbnail' src='" 
          + picFile.result + "'" + "title='" + picFile.name + "'/>";
        output.insertBefore(div, null);
      });
      picReader.readAsDataURL(file);
    }
  });
})();
</script>

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

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

Nexus 11.01.2018 21:49

рони, вероятно вы правы.

laimas 11.01.2018 21:55

Цитата:

Сообщение от Nexus
сохранять выбранные пользователем изображения в переменной

И оставить пользователя без памяти. )

рони 11.01.2018 21:58

LingVist,
копайте в сторону FormData

Nexus 11.01.2018 21:58

Цитата:

Сообщение от laimas
И оставить пользователя без памяти. )

Угу, как ни посмотри - наиболее геморойный вариант.

Nexus 11.01.2018 21:59

Теоретически должно работать.
<div id="files">
  <input type="file" multiple accept="image/*"/>
</div>
<div id="result"></div>
<style>
  .img_preview{display:inline-block;}
  .thumbnail{max-width:100px;}
  #files input{display:none;}
  #files input:last-child{display:block;}
</style>
<script>
(function(){
  if(!window.File || !window.FileList || !window.FileReader)
    return console.log("Your browser does not support File API");

  document.getElementById("files").addEventListener("change", function(event) {
    var clone = event.target.cloneNode(),
        files = event.target.files,
        output = document.getElementById("result");

    clone.value=null;
    this.appendChild(clone);
    this.focus();
    for(var i = 0; i < files.length; i++) {
      var file = files[i];
      if(!file.type.match('image'))
        continue;

      var picReader = new FileReader();
      picReader.addEventListener("load", function(event) {
        var picFile = event.target;
        var div = document.createElement("div");
        div.className = 'img_preview';
        div.innerHTML = "<img class='thumbnail' src='"
          + picFile.result + "'" + "title='" + picFile.name + "'/>";
        output.insertBefore(div, null);
      });
      picReader.readAsDataURL(file);
    }
  });
})();
</script>

laimas 11.01.2018 22:03

Цитата:

Сообщение от рони
копайте в сторону FormData

Вроде бы как проблема и так понятна - поле с мультивыбором, но это не означает, что оно запомнит все последовательные выборы. Так что проблема не в том как отправить, а в "борьбе с естественны". )

laimas 11.01.2018 22:04

Цитата:

Сообщение от Nexus
как ни посмотри - наиболее геморойный вариант

Drag & drop для поля с мультивыбором при выборе из разных каталогов, иначе никак.

рони 11.01.2018 22:25

Цитата:

Сообщение от laimas
Так что проблема не в том как отправить,

а в чём? разве нельзя добавить несколько картинок в FormData из output и отправить ajax

laimas 12.01.2018 05:34

Цитата:

Сообщение от рони
разве нельзя добавить несколько картинок в FormData из output и отправить ajax

Как base64?

Nexus 12.01.2018 08:07

Цитата:

Сообщение от laimas
Как base64?

Скорее всего файл не будет кодироваться в base64.
https://developer.mozilla.org/ru/doc...% D0%B8%D1%81

laimas 12.01.2018 08:36

Цитата:

Сообщение от Nexus
Скорее всего файл не будет кодироваться в base64.

Это почему?

picReader.readAsDataURL(file);

Взять из src base64 и отправить можно. Но это будет объем в три раза меньше разрешенного сервером, и это будут данные в $_POST.

Nexus 12.01.2018 09:48

Цитата:

Сообщение от laimas
Это почему?

FormData.append может принимать в кач-ве значения экземпляр класса File.
Сервер, по идее, должен получить файл не в глобальном объекте _post, но _files.

laimas 12.01.2018 09:51

Какой file, если речь об output, который html? )

Nexus 12.01.2018 10:17

laimas, виноват, упустил упоминание "output" в посте рони.

laimas 12.01.2018 10:20

Nexus,
можно и файлом передать, но для этого надо обратное - base64 -> Blob. А вот стоит ли это делать вместо добавления нового поля, это уже вопрос философский. )

рони 12.01.2018 10:26

Возможно я вас увёл в сторону, FormData.append все files (не знаю формат) дополнять при каждом change, а не из output.

laimas 12.01.2018 10:29

рони,
можно, но в этом случае нужно решать и вопросы связанные с отказом загружать что-то из ранее выбранного.

Nexus 12.01.2018 10:31

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

рони 12.01.2018 10:31

Цитата:

Сообщение от laimas
нужно решать и вопросы связанные с отказом загружать что-то из ранее выбранного

:yes:

рони 12.01.2018 10:33

:write:
https://developer.mozilla.org/ru/doc...ormData/delete

Nexus 12.01.2018 10:33

рони, FormData в памяти устройства же будет храниться?
Т.е. данные будут как в base64 в src preview, так и в объекте FormData единовременно?

laimas 12.01.2018 10:36

Цитата:

Сообщение от Nexus
нет никаких трудностей в том, чтобы на сервере обработать файл как post

Да, это не проблема, но есть одна неприятность в этом. Например, если сервером определено, что макс. размер загружаемого файла не более 2 МБ, то при загрузке файла как base64 это ограничение легко обойти.

laimas 12.01.2018 10:40

рони,
ну так надо связывать каждый отображаемый эскиз в output с данными в FormData. Именовать каждый элемент в наборе уникальным именем, это значит усложнить обработку на сервере. Работать с индексным набором, придется следить что имеется уже (имя поля ввода обязательно должно отражать массив - name[], в противном случае РНР отдаст последний в наборе файл).

Nexus 12.01.2018 10:41

laimas, проверить размер файла перед сохранением все же можно.

laimas 12.01.2018 10:45

Цитата:

Сообщение от Nexus
проверить размер файла перед сохранением все же можно.

Уже поздно, он уже на сервере и в тройном размере.

рони 12.01.2018 10:49

Цитата:

Сообщение от Nexus
Т.е. данные будут как в base64 в src preview, так и в объекте FormData единовременно?

не знаю

Nexus 12.01.2018 11:00

laimas, в php есть директива "post_max_size".
Правда как поступит сервер, если клиент превысит этот лимит я не знаю.

LingVist 12.01.2018 11:01

Nexus, предложенный Вами вариант также не работает, передается пустой массив.
В обработчике РНР для загрузки файлов в папку я использую цикл
$array = array();
        
        foreach ($request->file() as $file) {
            foreach ($file as $img) {
                $img->move(public_path().'/'.env('THEME').'/images/estate/'.$upload_dir, date('dmY_Hi').'_'.$img->getClientOriginalName());
                $img = date('dmY_Hi').'_'.$img->getClientOriginalName();	
                
                array_push($array, $img);
                }
            }


В массив $array добавляются имена изображений, потом закидываю их в БД одной строкой.

Может подобным образом можно создать массив и с самими картинками? Только как это сделать технически, я не знаю

Nexus 12.01.2018 11:02

LingVist, вы имя инпуту указали?

upd.
Цитата:

Сообщение от LingVist
foreach ($request->file() as $file) {
    foreach ($file as $img) {

А вы уверены, что клиент прислал изображения? :)

laimas 12.01.2018 11:04

Цитата:

Сообщение от Nexus
в php есть директива "post_max_size"

Это общий размер POST данных разрешенных для загрузки и этот размер должен быть больше чем общее разрешенное для загрузки количества файлов (по умолчанию 20) помноженные на разрешенный размер одного файла (по умолчанию 2 МБ), который никак не влияет на размер не бинарных данных.

Nexus 12.01.2018 11:14

laimas,
Цитата:

Сообщение от laimas
Уже поздно, он уже на сервере и в тройном размере.

Вы имели ввиду, что серверу может не хватить памяти для обработки запроса из-за чрезмерного размера изображения.
Я верно понял?

laimas 12.01.2018 11:22

Цитата:

Сообщение от Nexus
Вы имели ввиду, что серверу может не хватить памяти для обработки запроса из-за чрезмерного размера изображения.

Я имел ввиду ограничение, а по каким соображениям оно накладывается, это резиновый вопрос. )

LingVist 12.01.2018 11:28

Цитата:

Сообщение от Nexus (Сообщение 474963)
LingVist, вы имя инпуту указали?

А вы уверены, что клиент прислал изображения? :)

Да, с инпутом промашка вышла. Сейчас все работает нормально, большое Вам спасибо!

Alexandroppolus 12.01.2018 11:35

FileReader здесь не нужен. Правильнее использовать URL.createObjectURL, и не забыть про URL.revokeObjectURL.

Итого, никакого base64 тут не надо. Т.е. не будет копии файла в оперативной памяти.

Все выбираемые файлы складывать в массив, потом из этого массива добавить в FormData перед отправкой.


Часовой пояс GMT +3, время: 07:55.