javascript - 使用 JavaScript 从客户端上传压缩图像文件
问题描述
我正在尝试在一些低带宽设备上使用 JavaScript 在客户端压缩图像,而我目前使用 HTML5 File API 陷入困境。我是新手,如果我错过了重要的事情,请多多包涵。
我有一些输入标签,理想情况下应该打开移动相机,捕获单个图像,压缩并将文件发送到后端。虽然这可以通过启用多个上传的单个输入字段来完成,但我需要多个图像字段来根据某些类别隔离图像。
这是输入框:
<input type="file" name="file1" id="file1" capture="camera" accept="image/*">
<input type="file" name="file2" id="file2" capture="camera" accept="image/*">...
这是图像压缩逻辑:
// Takes upload element id ("file1") and a maxSize to resize, ideally on a change event
window.resizePhotos = function(id, maxSize){
var file = document.getElementById(id).files[0];
// Ensuring it's an image
if(file.type.match(/image.*/)) {
// Loading the image
var reader = new FileReader();
reader.onload = function (readerEvent) {
var image = new Image();
image.onload = function (imageEvent) {
// Resizing the image and keeping its aspect ratio
var canvas = document.createElement("canvas"),
max_size = maxSize,
width = image.width,
height = image.height;
if (width > height) {
if (width > max_size) {
height *= max_size / width;
width = max_size;
}
} else {
if (height > max_size) {
width *= max_size / height;
height = max_size;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext("2d").drawImage(image, 0, 0, width, height);
var dataUrl = canvas.toDataURL("image/jpeg");
var resizedImage = dataURLToBlob(dataUrl);
$.event.trigger({
type: "imageResized",
blob: resizedImage,
url: dataUrl
});
}
image.src = readerEvent.target.result;
}
reader.readAsDataURL(file);
}
};
// Function to convert a canvas to a BLOB
var dataURLToBlob = function(dataURL) {
var BASE64_MARKER = ';base64,';
if (dataURL.indexOf(BASE64_MARKER) == -1) {
var parts = dataURL.split(',');
var contentType = parts[0].split(':')[1];
var raw = parts[1];
return new Blob([raw], {type: contentType});
}
var parts = dataURL.split(BASE64_MARKER);
var contentType = parts[0].split(':')[1];
var raw = window.atob(parts[1]);
var rawLength = raw.length;
var uInt8Array = new Uint8Array(rawLength);
for (var i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], {type: contentType});
}
// Handling image resized events
$(document).on("imageResized", function (event) {
if (event.blob && event.url) {
document.getElementById('file1').files[0] = event.url; // --> Tried this, did not work
document.getElementById('file1').files[0].value = (URL || webkitURL).createObjectURL(event.blob); // --> Tried doing this looking at some other answers but did not work
console.log(document.getElementById('file1').files[0]); // Original file is loading fine
console.log(event.url); // Image compression is working correctly and producing the base64 data
}
});
$(window).on("load", function() {
// Resets the value to when navigating away from the page and choosing to upload the same file (extra feature)
$("#file1").on("click touchstart" , function(){
$(this).val("");
});
// Action triggers when user has selected any file
$("#file1").change(function(e) {
resizePhotos("file1", 1024)
});
});
在 PHP 脚本中,我通常会尝试从 POST 请求中捕获文件,例如:
$file1 = $_FILES["file1"]["tmp_name"];
$file2 = $_FILES["file2"]["tmp_name"];
...
但这不起作用,因为它在 tmp 目录中查找原始用户选择的文件(例如,在我的情况下,实际的临时文件是 C:\xampp\tmp\php25CB.tmp )
我尝试过的一件事是将输入字段放在表单标签之外,使用按钮启用点击行为,并在表单中使用修改后的数据创建新的输入字段,例如:
var newinput = document.createElement("input");
newinput.type = 'file';
newinput.name = 'file1';
newinput.files[0] = event.url;
document.getElementById('parentdiv').appendChild(newinput);
不用说,这没有任何效果,PHP 脚本无法识别任何文件。
请指导我并建议 JavaScript/PHP 脚本中所需的任何更改,以便我可以接受修改后的文件,而不是输入字段中原始用户上传的文件。
解决方案
- 您只能使用另一个列表更改文件输入值,
方法如下:https ://stackoverflow.com/a/52079109/1008999 (也在示例中) - 使用FileReader 是浪费时间、CPU、编码和解码以及 RAM ......请
改用URL.createObjectURL - 不要使用 canvas.toDataURL...改用canvas.toBlob
- 画布压缩不好,请阅读早期评论并查看jsfiddle proff ...
如果您坚持使用画布尝试缩小尺寸,那么- 先试试看图片大小是否合理
- 比较预先存在的图像
file.size
是否小于canvas.toBlob提供的图像,然后选择您想要旧图像还是新图像。 - 如果调整图像大小还不够,请查看此解决方案,该解决方案会更改质量,直到满足所需的文件大小和图像方面。
没有任何测试,这也是我重构代码的方式:
/**
* @params {File[]} files Array of files to add to the FileList
* @return {FileList}
*/
function fileListItems (files) {
var b = new ClipboardEvent('').clipboardData || new DataTransfer()
for (var i = 0, len = files.length; i<len; i++) b.items.add(files[i])
return b.files
}
// Takes upload element id ("file1") and a maxSize to resize, ideally on a change event
window.resizePhotos = async function resizePhotos (input, maxSize) {
const file = input.files
if (!file || !file.type.match(/image.*/)) return
const image = new Image()
const canvas = document.createElement('canvas')
const max_size = maxSize
image.src = URL.createObjectURL(file)
await image.decode()
let width = image.width
let height = image.height
// Resizing the image and keeping its aspect ratio
if (width > height) {
if (width > max_size) {
height *= max_size / width
width = max_size
}
} else {
if (height > max_size) {
width *= max_size / height
height = max_size
}
}
canvas.width = width
canvas.height = height
canvas.getContext('2d').drawImage(image, 0, 0, width, height)
const resizedImage = await new Promise(rs => canvas.toBlob(rs, 'image/jpeg', 1))
// PS: You might have to disable the event listener as this might case a change event
// and triggering this function over and over in a loop otherwise
input.files = fileListItems([
new File([resizedImage], file.name, { type: resizedImage.type })
])
}
jQuery($ => {
// Resets the value to when navigating away from the page and choosing to upload the same file (extra feature)
$('#file1').on('click touchstart' , function(){
$(this).val('')
})
// Action triggers when user has selected any file
$('#file1').change(function(e) {
resizePhotos(this, 1024)
})
})
推荐阅读
- php - 谷歌驱动器php在下载前获取文件名
- python - 为什么它在 CentOS wx.html2 中不可用。WebView控件?
- android - Dagger - Hilt:我们是否需要用@AndroidEntryPoint 标记所有活动
- azure - 2021 年,导出 Azure 自动化 AzureRunAsConnection 用于本地调试的证书的最快/最简单方法是什么?
- python - 没有输出 tkinter
- c++ - 在 C++ 中处理大哈希表
- python - 我为我的网站制作了 Hashtag 系统,但是在使用了“'”或“;”等特殊字符后,它在 Django 中创建了#hashtag
- session - 如何在 Postman 中获取 Cookie?
- ruby - Ruby:逐行而不是整个文件写入CSV
- docker - 构建我自己的 docker 映像并将其部署到生产服务器?