Удаление Объекта Image() Из Памяти При Щелчке По Событию Родственного Элемента — JavaScript

#javascript #image #events #memory

Вопрос:

У меня есть средство предварительного просмотра изображений, которое использует объект JavaScript Image() для предварительного просмотра изображений до их обработки с помощью PHP. У меня есть div, содержащий SVG-графику «x», которая предназначена для события щелчка, чтобы удалить изображение.

В приведенном ниже коде в нижней части функции он использует evt.target и, по сути, удаляет родительский элемент, внутри которого находится каждое изображение, чтобы удалить изображение, если пользователь пожелает это сделать.

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

Я попытался установить само изображение ( thumbnailElement в JavaScript) равным нулю и установить его атрибут src в пустую строку, но это не работает.

Каков наилучший способ предотвратить обработку этих предварительных просмотров удаленных изображений?

В приведенном ниже коде я поменял SVG-графику на » x «на букву «x», чтобы облегчить ее чтение.

ПРИМЕЧАНИЕ: Я показал ниже весь загрузчик изображений — но это заключительная часть JS // Delete Images , с которой мне нужна помощь.

Кодовое обозначение: https://codepen.io/emilychews/pen/WNjZVGZ

 const dropZone = document.getElementById('drop-zone'),
    showSelectedImages = document.getElementById('show-selected-images'),
    fileUploader = document.getElementById('standard-upload-files')  

dropZone.addEventListener("click", (evt) => {
    // assigns the dropzone to the hidden input element so when you click 'select files' it brings up a file picker window
    fileUploader.click();
});

// Prevent browser default when draging over
dropZone.addEventListener("dragover", (evt) => {
    evt.preventDefault();
});

fileUploader.addEventListener("change", (evt) => {
    // Clear the already selected images
    showSelectedImages.innerHTML = "";
    // this function is further down but declared here and shows a thumbnail of the image
    [...fileUploader.files].forEach(updateThumbnail);
});

dropZone.addEventListener("drop", (evt) => {
    evt.preventDefault();
    // Clear the already selected images
    showSelectedImages.innerHTML = "";

    // assign dropped files to the hidden input element
    if (evt.dataTransfer.files.length) {
        fileUploader.files = evt.dataTransfer.files;
    }

    // function is declared here but written further down
    [...evt.dataTransfer.files].forEach(updateThumbnail);


});

// updateThumbnail function that needs to be able to handle multiple files
function updateThumbnail(file) {
    
    if (file.type.startsWith("image/")) {

        const uploadImageWrapper = document.createElement('article'),
        removeImage = document.createElement('div'),
        thumbnailElement = new Image();

        // 'x' that deletes the image
        removeImage.classList.add("remove-image");
        removeImage.innerHTML = 'x';

        // image thumbnail
        thumbnailElement.classList.add("drop-zone__thumb");
        thumbnailElement.src = URL.createObjectURL(file);

        // appending elements
        showSelectedImages.append(uploadImageWrapper)   // <article> element
        uploadImageWrapper.append(removeImage)          // 'x' to delete
        uploadImageWrapper.append(thumbnailElement);    // image thumbnail
        
        // Delete images

        removeImage.addEventListener('click', (evt) => {
            if(evt.target) {
                var deleteImage = removeImage.closest('article');
                deleteImage.remove()
            }
        })
    }

} // end of 'updateThumbnail' function 
 body {
  margin: 0;
  display: flex;
  justify-content: center;
  width: 100%;
}

form {
  width: 30%;
}

#drop-zone {
  border: 1px dashed;
  width: 100%;
  padding: 1rem;
  margin-bottom: 1rem;
}

.select-files {
  text-decoration: underline;
  cursor: pointer;
}

/* image that is preview prior to form submit*/
.drop-zone__thumb {
  width: 200px;
  height: auto;
  display: block;
}

#remove-x {
  width: 1rem;
  height: 1rem;
}

#submit-images {
  margin: 1rem 0;
}

#show-selected-images {
  display: flex;
} 
 <form id="upload-images-form" enctype="multipart/form-data" method="post">
  <h1>Upload Your Images</h1>
  <div id="drop-zone" class="drop-zone flex">
    <p class="td text-center">DRAG AND DROP IMAGES HERE</p>
    <p class="td text-center" style="margin: 0">Or</p>
    <p class="tl text-center select-files text-bold pointer">Select Files</p>
  </div>
  <input id="standard-upload-files" style="display:none" style="min-width: 100%" type="file" name="standard-upload-files[]" multiple>
  <input type="submit" name="submit-images" id="submit-images" value="SUBMIT IMAGES">
  <div id="show-selected-images"></div>
</form> 

Комментарии:

1. Если элемент удален из DOM, он исчезнет, если только в вашем случае он не является ссылкой, хранящейся в переменной javascript deleteImage . Что касается «представления», я понятия не имею, о чем вы говорите. представление чего и как?

2. Используйте Element.remove(); , то до тех пор, пока у вас нет никаких ссылок на Element сборку мусора, это может произойти.

3. Так что, может быть, вы могли бы попробовать использовать removeImage.parentElement.remove() , не храня его в переменной, или даже лучше: removeImage.parentElement.parentNode.removeChild(removeImage.parentElement)

4. @vanowm Я отредактировал вопрос в отношении вашего первого комментария, и цепочка методов не имела никакого значения.

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

Ответ №1:

Проблема:

Вы не обновляете fileUploader.files , когда вы add/remove отправляете товар.

Решение

Каждый раз, когда вам drop/remove нужен файл, вам нужно обновлять ввод загрузчика файлов. Первый шаг-создать функцию для обработки объекта списка файлов и изменить только две строки кода:

 //--> to create a FileList
function getFileListItems (files) {
  var transferObject = new ClipboardEvent("").clipboardData || new DataTransfer()
  for (var i = 0; i<files.length; i  ) transferObject.items.add(files[i])
  return transferObject.files;
}

 
 

Добавление файлов во время события удаления:

  //--> Updating files during drop event
 fileUploader.files = getFileListItems([...fileUploader.files, ...evt.dataTransfer.files]);

 

Удаление файла:

 fileUploader.files = getFileListItems([...fileUploader.files].filter(f => file!==f));
 

Смотрите полный рабочий пример:

 const dropZone = document.getElementById("drop-zone"),
  showSelectedImages = document.getElementById("show-selected-images"),
  fileUploader = document.getElementById("standard-upload-files");

dropZone.addEventListener("click", (evt) => {
  // assigns the dropzone to the hidden input element so when you click 'select files' it brings up a file picker window
  fileUploader.click();
});

// Prevent browser default when draging over
dropZone.addEventListener("dragover", (evt) => {
  evt.preventDefault();
});

fileUploader.addEventListener("change", (evt) => {
  // this function is further down but declared here and shows a thumbnail of the image
  [...fileUploader.files].forEach(updateThumbnail);
});

function getFileListItems(files) {
  var transferObject = new ClipboardEvent("").clipboardData || new DataTransfer()
  for (var i = 0; i < files.length; i  ) transferObject.items.add(files[i])
  return transferObject.files;
}

dropZone.addEventListener("drop", (evt) => {
  evt.preventDefault();
  // assign dropped files to the hidden input element
  if (evt.dataTransfer.files.length) {
    fileUploader.files = getFileListItems([...fileUploader.files, ...evt.dataTransfer.files]);
  }
  // function is declared here but written further down
  [...evt.dataTransfer.files].forEach(updateThumbnail);
});

// updateThumbnail function that needs to be able to handle multiple files
function updateThumbnail(file) {
  if (file.type.startsWith("image/")) {
    let uploadImageWrapper = document.createElement("article"),
      removeImage = document.createElement("div"),
      thumbnailElement = new Image();

    // 'x' that deletes the image
    removeImage.classList.add("remove-image");
    removeImage.innerHTML =
      '<svg id="remove-x" viewBox="0 0 150 150"><path fill="#000" d="M147.23,133.89a9.43,9.43,0,1,1-13.33,13.34L75,88.34,16.1,147.23A9.43,9.43,0,1,1,2.76,133.89L61.66,75,2.76,16.09A9.43,9.43,0,0,1,16.1,2.77L75,61.66,133.9,2.77a9.42,9.42,0,1,1,13.33,13.32L88.33,75Z"/></svg>';

    // image thumbnail
    thumbnailElement.classList.add("drop-zone__thumb");
    thumbnailElement.src = URL.createObjectURL(file);

    // appending elements
    showSelectedImages.append(uploadImageWrapper); // <article> element
    uploadImageWrapper.append(removeImage); // 'x' to delete
    uploadImageWrapper.append(thumbnailElement); // image thumbnail

    // Delete images
    removeImage.addEventListener("click", (evt) => {
      if (evt.target) {
        var deleteImage = removeImage.parentElement;
        deleteImage.remove();
        fileUploader.files = getFileListItems([...fileUploader.files].filter(f => file !== f));
      }
    });
  }
} // end of 'updateThumbnail' function 
 body {
  margin: 0;
  display: flex;
  justify-content: center;
  width: 100%;
}

form {
  width: 30%;
}

#drop-zone {
  border: 1px dashed;
  width: 100%;
  padding: 1rem;
  margin-bottom: 1rem;
}

.select-files {
  text-decoration: underline;
  cursor: pointer;
}


/* image that is preview prior to form submit*/

.drop-zone__thumb {
  width: 200px;
  height: auto;
  display: block;
}

#remove-x {
  width: 1rem;
  height: 1rem;
}

#submit-images {
  margin: 1rem 0;
}

#show-selected-images {
  display: flex;
} 
 <form id="upload-images-form" enctype="multipart/form-data" method="post">
  <h1>Upload Your Images</h1>
  <div id="drop-zone" class="drop-zone flex">
    <p class="td text-center">DRAG AND DROP IMAGES HERE</p>
    <p class="td text-center" style="margin: 0">Or</p>
    <p class="tl text-center select-files text-bold pointer">Select Files</p>
  </div>
  <input id="standard-upload-files" style="display:none" style="min-width: 100%" type="file" name="standard-upload-files[]" multiple>
  <input type="submit" name="submit-images" id="submit-images" value="SUBMIT IMAGES">
  <div id="show-selected-images"></div>

</form> 

Рабочий пример

Комментарии:

1. Привет @lissettdm Спасибо за это — это отлично работает из коробки. Однако у меня есть непредвиденная проблема — dataTransfer API только что появился в Safari, и, похоже, мне понадобится запасной вариант. Будет ли это настоящей головной болью для потенциальной адаптации/изменений ? Возможно, мне следует задать это в качестве отдельного вопроса?

2. Привет @TheChewy, добро пожаловать, может быть, с помощью полифилла вы сможете заставить это работать

3. Спасибо. Я нашел только один заполнитель, который, похоже, просто не работает. Хотя сейчас он находится в Safari (14.1), его все еще нет в iOS (14.7), что странно. Так раздражает. Возможно, мне придется подумать об обнаружении функций, и если dataTransfer.files не поддерживается, удалите элемент перетаскивания и просто используйте стандартный элемент выбора/загрузки файлов.

4. Я нашел это gist.github.com/saschanaz/293f9c7698776c8f3510aff703209d24..I не проверял это.

5. да, это то, что я пробовал. Я не мог заставить его работать.

Ответ №2:

Это связано с тем, что при отправке формы она отправляет не каждый HTML-элемент в форме, а только значения input ,,, textarea , button и некоторые другие, которые имеют name атрибут.

Так что вам тоже нужно очистить поле файла input :

 const showSelectedImages = document.getElementById("preview");
const input = document.getElementById("test");

function updateThumbnail(file) {
  if (file.type.startsWith("image/")) {

    // image and 'x' to delete wrapper 
    const uploadImageWrapper = document.createElement('article')

    // div that holds the 'x' to delete
    const removeImage = document.createElement('div')

    // image preview element
    thumbnailElement = new Image()

    // 'x' that deletes the image
    removeImage.classList.add("remove-image")
    removeImage.innerHTML = 'x'

    // image thumbnail
    thumbnailElement.classList.add("drop-zone__thumb")
    thumbnailElement.src = URL.createObjectURL(file)

    // appending elements
    showSelectedImages.append(uploadImageWrapper) // <article> element
    uploadImageWrapper.append(removeImage) // 'x' to delete
    uploadImageWrapper.append(thumbnailElement) // image thumbnail

    // Delete Images when the 'x' div is clicked
    removeImage.addEventListener('click', (evt) => {
      if (evt.target) {
        var deleteImage = removeImage.parentElement
        deleteImage.remove();

        /* for single file input is enough clear value property */

        //        input.value = null;

        /* for multiple files input we'll need recreate new files list excluding deleted file */
        const dt = new DataTransfer();
        for (let f of input.files) {
          if (f !== file)
            dt.items.add(f);
        }

        input.files = dt.files;
      }
    })
  }

}

function updateThumbnails(files) {
  showSelectedImages.innerHTML = ""; //remove all previous previews
  for (let f of files)
    updateThumbnail(f);
}

function showform(form) {
  const list = {};
  for (let i of [...new FormData(form).entries()]) {
    const key = i[0].match(/^([^[] )[]$/);
    if (key) {
      if (!list[key[1]])
        list[key[1]] = [];
      list[key[1]][list[key[1]].length] = i[1];
    } else
      list[i[0]] = i[1];
  }
  console.log(list)
  return false;
} 
 .remove-image {
  cursor: pointer;
}

article {
  display: inline-block;
} 
 <form type="post" onsubmit="return showform(this);">
  <input type="hidden" name="myHiddenInput" value="blah">
  <input type="file" name="test[]" id="test" oninput="updateThumbnails(this.files)" multiple>
  <span id="preview"></span>
  <button type="submit">submit</button>
</form> 

Комментарии:

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

2. Для любых загрузчиков изображений требуется поле ввода файла. Если вы посмотрите на HTML, вы увидите <input id="standard-upload-files" style="display:none" style="min-width: 100%" type="file" name="standard-upload-files[]" multiple> , что просто удалите display: none из него и перетащите изображение, вы увидите, что на этом входе будут показаны файлы.

3. Я думаю, что проблема в oninput="updateThumbnail(this.files[0]) том, что я изменил его oninput="updateThumbnail(this.files) на обработку нескольких файлов, но затем при нажатии кнопки «Отправить» ничего не загружается?

4. Это был всего лишь пример. Ваша updateThumbnail() функция принимает только 1 файл одновременно. Исправление исходной проблемы заключается document.getElementById("test").value = null; в том, чтобы очистить поле ввода.

5. Но моя updateThumbnail() функция не принимает только один файл за раз? Я в замешательстве?