javascript - 图片上传器在 Safari 中不工作 - 可能的解决方法 (JavaScript)
问题描述
我一直在构建一个理论上已经完成的图像上传器,但它在 Safari 中不起作用。
我最初认为问题是因为DataTransfer
,或者更具体地说dataTransfer.files
,不受支持,但现在桌面 Safari(版本 14.1 +)支持此功能。尽管在 iOS 中根本没有支持,但我将在 iOS 上仅使用文件选择器(无拖放),所以这不是问题。
桌面 Safari 上的问题/场景
当用户单击“选择文件”链接并提交图像时,一切正常。
当用户拖放图像时,文件预览图像被复制(例如,2 个图像显示 4 个预览)。提交时,只上传了 2 张图片(这显然是正确的)。
当用户单击“选择文件”链接而不是使用拖放功能,然后从图像预览中删除图像时,图像预览会被复制(类似于上面第 2 点中的方式)。
从逻辑上讲,这会让我认为问题出在change
事件侦听器上,但我似乎无法修复它。
任何帮助将不胜感激。
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;
}
/* images that are previewed prior to form submission*/
.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>
解决方案
问题是 Safari 在您.files
设置<input>
. (错误 180465)
鉴于他们确实同步触发了该事件,您可以使用一个简单的标志来解决这个问题:
let ignoreEvent = false; // our flag
const inp = document.querySelector("input");
inp.onchange = (evt) => {
if (ignoreEvent) {
console.log("this event should be ignored");
}
else {
console.log("A true change event");
}
};
const dT = new DataTransfer();
dT.items.add(new File(['foo'], 'programmatically_created.txt'));
// right before setting the .files
ignoreEvent = true;
inp.files = dT.files;
// the change event fired synchronously,
// lower the flag down
ignoreEvent = false;
<input type="file" id="inp">
在 Safari 中将输出“应忽略此事件”。
现在,我不禁提醒,设置类似FileList
的设置<input>
仍然是一种黑客行为。你真的不应该在生产中使用它。 (我确实修改了我的答案以使其更清楚。)
因此,请使用简单的 Array 和FormData进行上传。此外,由于您甚至没有显示原始文件选择器<input>
。
您显然已经复制粘贴了您不理解的先前答案,因此为避免您再次陷入此(可理解的)陷阱,我将仅强调您实施所需的关键点,我不会给出你是一个工作的例子,故意的。
所以首先你需要定义一个 Array ( []
) 来存储你的用户选择的文件并且可以被以下所有函数访问。
在放置和输入的更改事件中,您将使用新选择的文件更新此数组(您可以附加新文件或替换它们,这是您的决定)。
然后,您将为<form>
元素的submit
事件添加一个新的事件侦听器。单击提交按钮时将触发此事件。
它的默认操作是将<form>
' 的内容发布到您的服务器,但由于我们想要包含我们自己的 Array 而不是<input>
' 的文件列表(因为它实际上可能是空的),我们不希望发生此默认操作,所以我们将调用event.preventDefault()
为了避免这种情况。
现在,我们仍然想向服务器发送一些东西,所以我们将从头开始构建一个新表单。
这就是我们将创建一个新FormData
对象的地方,并使用它的append()
方法来存储我们的文件。该方法的第一个参数应该是字段名,也就是<input>
的name
属性值。第二个应该是单个文件,因此我们需要调用此方法的次数与数组中的项目一样多。由于我们处理File对象,我们不需要第三个参数。
一旦我们的 FormData 完成,我们只需将其上传到服务器。
为此,在现代浏览器中,您可以简单地使用该fetch()
方法,将 URL 传递给服务器,以及一个看起来大致如下的选项包
{
method: "POST",
body: formData // the FormData object we just created
}
如果您需要支持较旧的浏览器,您也可以对 XMLHttpRequest 对象执行相同的操作,在其.send(formData)
方法中传递 FormData。
由于尽管逐步解释,OP显然无法摆脱这一点,所以这里有一个 jsfiddle。
推荐阅读
- three.js - RGBELoader 纹理质量低 - 256 x 256
- arrays - 在 PyCuda 的 SourceModule 中创建数组
- java - Eclipse IDE:无法加载资源,因为应用程序传输安全策略需要使用安全连接
- json - 使用 JQ 将 json 转换为 csv 时向每一行添加字符串
- python - 请帮我用选择描述写一个程序,根据用户输入的月份显示对应的四个季节
- reactjs - 读取 excel 文件时出现问题(fs readFilesync 错误)
- cakephp - 将蛋糕 2 升级到 3
- sql - 当日期字段为空时,我想将其设置为今天的日期
- sas - 输入文本文件,缺少输出日期格式
- python - AttributeError:模块“django.db.models”没有属性“BigAutoField”