我正在创建一个屏幕截图提交表单,我想在将图像上传到我的服务器之前对其进行压缩。我已经有了流程,所以它可以工作,但是每当我第一次测试它时,我尝试提交它都会失败。再次单击“提交”按钮,无需更改任何内容,无需重新选择文件,只需再次单击“提交”,就如我所期望的那样成功了。
我正在使用 Next.js,使用 swr 和 react-toastify 分别处理数据获取/缓存和用户通知。
这是我的压缩功能。
const compressImage = imgData =>
new Promise((resolve, reject) => {
const img = document.createElement("img");
img.src = imgData;
console.log(img);
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
const originalWidth = img.width;
const originalHeight = img.height;
console.log(originalWidth, originalHeight);
const baseWidth = 480;
const baseHeight = 270;
const canvasWidth = Math.min(baseWidth, ((originalWidth * baseHeight) / originalHeight) | 0);
const canvasHeight = Math.min(baseHeight, ((originalHeight * baseWidth) / originalWidth) | 0);
console.log(canvasWidth, canvasHeight);
canvas.width = Math.min(originalWidth, canvasWidth);
canvas.height = Math.min(originalHeight, canvasHeight);
console.log(canvas.width, canvas.height);
try {
context.drawImage(img, 0, 0, canvasWidth, canvasHeight);
} catch (err) {
return reject(err);
}
// Reduce quality
canvas.toBlob(blob => {
if (blob) resolve(blob);
else reject("No blob");
}, "image/jpeg");
});
我怀疑问题要么出在我创建 img 元素的方式上,要么出在此处之前的某个地方,这就是它显而易见的地方。
当我检查日志时,我看到第一次尝试显示 src 设置正确,但大小为 0 0,然后向下传播,我的期望是,当画布大小为 0 时,没有任何内容可放入 blob 中结束。
<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD…">
0 0
0 0
0 0
第二次提交给出的是我期望看到的日志。
<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD…">
1920 1080
480 270
480 270
我有点迷失于寻找什么或改变什么。感觉好像有一个react hook正在使用过时的数据,但是为什么img src在压缩函数中是正确的。
我创建了此表单用于提交:
<Modal.Body>
{screenshot && (
<img
src={screenshot}
alt="Submitted Screenshot"
width="100%"
style={{ objectFit: "contain" }}
/>
)}
<form
id="screenshotForm"
action={async formData => {
setSubmitting(true);
const screenshotFile = formData.get("screenshot");
// Is this actually redundant?
const screenshotData = await new Promise(resolve => {
const fr = new FileReader();
fr.addEventListener("load", e => resolve(e.target.result));
fr.readAsDataURL(screenshotFile);
});
try {
const compressedData = await compressImage(screenshotData);
const blobUrl = URL.createObjectURL(compressedData);
setScreenshot(blobUrl);
const formData = new FormData();
formData.append("image", compressedData);
formData.append("beatmapId", selectedMap.id);
formData.append("mods", selectedMap.mods);
await toast.promise(
mutate("/api/db/player", () => uploadScreenshot(formData), {
optimisticData: oldData => {
const updatedMaplist = oldData.maps.current;
const index = updatedMaplist.findIndex(
m => m.id === selectedMap.id && m.mods === selectedMap.mods
);
updatedMaplist[index].screenshot = compressedData.arrayBuffer();
return {
...oldData,
maps: {
...oldData.maps,
current: updatedMaplist
}
};
},
populateCache: (result, oldData) => ({
...oldData,
maps: {
...oldData.maps,
current: result
}
})
}),
{
pending: "Uploading",
success: "Image uploaded",
error: "Unable to upload image"
}
);
} catch (err) {
toast.error("Unable to upload image");
console.error(err);
}
setSubmitting(false);
}}
>
<FormLabel htmlFor="imageUpload">Upload Screenshot</FormLabel>
<FormControl type="file" accept="image/*" id="imageUpload" name="screenshot" />
</form>
</Modal.Body>
<Modal.Footer>
<Button type="submit" form="screenshotForm" disabled={submitting}>
Submit {submitting && <Spinner size="sm" />}
</Button>
<Button onClick={() => setShowModal(false)}>Done</Button>
</Modal.Footer>
这两个都在page.js文件中,声明为“使用客户端”;
uploadScreenshot 是从“./actions”导入的,即“使用服务器”;
另一个可能的症状是
submitting
所示按钮上的微调器永远不可见。但如果这是一个单独的问题,那么我在这里不关心它。
通常
IMG
元素在元素上有一个 onload
或 load
事件处理程序,用于在读取图像文件内容后对其进行处理。第一次压缩图像的尝试可能会失败,因为尚未读取图像,而第二次尝试会成功,因为对内存缓存中保存的图像数据进行了某种同步处理。 但这也许很大。
在图像元素上提供
load
事件处理程序,并在将图像写入画布之前等待它触发。
在图像元素上提供“错误”事件处理程序来处理错误情况。