#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()
функция не принимает только один файл за раз? Я в замешательстве?