我在 stackoverflow 上找到了一些不同的帖子甚至问题来回答这个问题。我基本上正在实现与这篇文章相同的事情。
这是我的问题。当我上传照片时,我还需要提交表格的其余部分。这是我的 html:
<form id="uploadImageForm" enctype="multipart/form-data">
<input name="imagefile[]" type="file" id="takePictureField" accept="image/*" onchange="uploadPhotos(\'#{imageUploadUrl}\')" />
<input id="name" value="#{name}" />
... a few more inputs ...
</form>
以前,我不需要调整图像大小,所以我的 javascript 看起来像这样:
window.uploadPhotos = function(url){
var data = new FormData($("form[id*='uploadImageForm']")[0]);
$.ajax({
url: url,
data: data,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(data){
... handle error...
}
}
});
};
这一切都很好......现在我需要调整图像大小......我如何替换表单中的图像,以便发布调整大小的图像而不是上传的图像?
window.uploadPhotos = function(url){
var resizedImage;
// Read in file
var file = event.target.files[0];
// Ensure it's an image
if(file.type.match(/image.*/)) {
console.log('An image has been loaded');
// Load the image
var reader = new FileReader();
reader.onload = function (readerEvent) {
var image = new Image();
image.onload = function (imageEvent) {
// Resize the image
var canvas = document.createElement('canvas'),
max_size = 1200,
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);
resizedImage = canvas.toDataURL('image/jpeg');
}
image.src = readerEvent.target.result;
}
reader.readAsDataURL(file);
}
// TODO: Need some logic here to switch out which photo is being posted...
var data = new FormData($("form[id*='uploadImageForm']")[0]);
$.ajax({
url: url,
data: data,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(data){
... handle error...
}
}
});
};
我考虑过将文件输入移出表单,并在表单中设置一个隐藏输入,将其值设置为调整大小的图像的值...但我想知道是否可以替换图像这已经在表格中了。
这就是我最终所做的,效果很好。
首先,我将文件输入移到表单之外,以便不提交:
<input name="imagefile[]" type="file" id="takePictureField" accept="image/*" onchange="uploadPhotos(\'#{imageUploadUrl}\')" />
<form id="uploadImageForm" enctype="multipart/form-data">
<input id="name" value="#{name}" />
... a few more inputs ...
</form>
然后我更改了
uploadPhotos
函数以仅处理调整大小:
window.uploadPhotos = function(url){
// Read in file
var file = event.target.files[0];
// Ensure it's an image
if(file.type.match(/image.*/)) {
console.log('An image has been loaded');
// Load the image
var reader = new FileReader();
reader.onload = function (readerEvent) {
var image = new Image();
image.onload = function (imageEvent) {
// Resize the image
var canvas = document.createElement('canvas'),
max_size = 544,// TODO : pull max size from a site config
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);
}
};
如您所见,我使用
canvas.toDataURL('image/jpeg');
将调整大小的图像更改为 dataUrl,然后调用函数 dataURLToBlob(dataUrl);
将 dataUrl 转换为 blob,然后将其附加到表单中。创建 blob 后,我会触发一个自定义事件。这是创建 blob 的函数:
/* Utility 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});
}
/* End Utility function to convert a canvas to a BLOB */
最后,这是我的事件处理程序,它从自定义事件中获取 blob,附加表单,然后提交它。
/* Handle image resized events */
$(document).on("imageResized", function (event) {
var data = new FormData($("form[id*='uploadImageForm']")[0]);
if (event.blob && event.url) {
data.append('image_data', event.blob);
$.ajax({
url: event.url,
data: data,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(data){
//handle errors...
}
});
}
});
如果有兴趣,我制作了打字稿版本:
interface IResizeImageOptions {
maxSize: number;
file: File;
}
const resizeImage = (settings: IResizeImageOptions) => {
const file = settings.file;
const maxSize = settings.maxSize;
const reader = new FileReader();
const image = new Image();
const canvas = document.createElement('canvas');
const dataURItoBlob = (dataURI: string) => {
const bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ?
atob(dataURI.split(',')[1]) :
unescape(dataURI.split(',')[1]);
const mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
const max = bytes.length;
const ia = new Uint8Array(max);
for (var i = 0; i < max; i++) ia[i] = bytes.charCodeAt(i);
return new Blob([ia], {type:mime});
};
const resize = () => {
let width = image.width;
let height = image.height;
if (width > height) {
if (width > maxSize) {
height *= maxSize / width;
width = maxSize;
}
} else {
if (height > maxSize) {
width *= maxSize / height;
height = maxSize;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
let dataUrl = canvas.toDataURL('image/jpeg');
return dataURItoBlob(dataUrl);
};
return new Promise((ok, no) => {
if (!file.type.match(/image.*/)) {
no(new Error("Not an image"));
return;
}
reader.onload = (readerEvent: any) => {
image.onload = () => ok(resize());
image.src = readerEvent.target.result;
};
reader.readAsDataURL(file);
})
};
这是 javascript 结果:
var resizeImage = function (settings) {
var file = settings.file;
var maxSize = settings.maxSize;
var reader = new FileReader();
var image = new Image();
var canvas = document.createElement('canvas');
var dataURItoBlob = function (dataURI) {
var bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ?
atob(dataURI.split(',')[1]) :
unescape(dataURI.split(',')[1]);
var mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
var max = bytes.length;
var ia = new Uint8Array(max);
for (var i = 0; i < max; i++)
ia[i] = bytes.charCodeAt(i);
return new Blob([ia], { type: mime });
};
var resize = function () {
var width = image.width;
var height = image.height;
if (width > height) {
if (width > maxSize) {
height *= maxSize / width;
width = maxSize;
}
} else {
if (height > maxSize) {
width *= maxSize / height;
height = maxSize;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
var dataUrl = canvas.toDataURL('image/jpeg');
return dataURItoBlob(dataUrl);
};
return new Promise(function (ok, no) {
if (!file.type.match(/image.*/)) {
no(new Error("Not an image"));
return;
}
reader.onload = function (readerEvent) {
image.onload = function () { return ok(resize()); };
image.src = readerEvent.target.result;
};
reader.readAsDataURL(file);
});
};
用法如下:
resizeImage({
file: $image.files[0],
maxSize: 500
}).then(function (resizedImage) {
console.log("upload resized image")
}).catch(function (err) {
console.error(err);
});
或(
async
/await
):
const config = {
file: $image.files[0],
maxSize: 500
};
const resizedImage = await resizeImage(config)
console.log("upload resized image")
2022 年,我们将提供一些新的 API。这是我想出的解决方案。我们不必搞乱 FileReader API 或图像加载回调。
以下代码接受文件对象或 Blob 对象,并输出经过裁剪、居中、调整大小的图像的 blob,并将其转换为 webp。
export default async (file, size) => {
size ??= 256
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = size
canvas.height = size
const bitmap = await createImageBitmap(file)
const { width, height } = bitmap
const ratio = Math.max(size / width, size / height)
const x = (size - (width * ratio)) / 2
const y = (size - (height * ratio)) / 2
ctx.drawImage(bitmap, 0, 0, width, height, x, y, width * ratio, height * ratio)
return new Promise(resolve => {
canvas.toBlob(blob => {
resolve(blob)
}, 'image/webp', 1)
})
}
如果你们中的一些人像我一样遇到方向问题,我已将此处的解决方案与 exif 方向修复结合起来
https://gist.github.com/SagiMedina/f00a57de4e211456225d3114fd10b0d0
import EXIF from 'exif-js';
const hasBlobConstructor = typeof (Blob) !== 'undefined' && (function checkBlobConstructor() {
try {
return Boolean(new Blob());
} catch (error) {
return false;
}
}());
const hasArrayBufferViewSupport = hasBlobConstructor && typeof (Uint8Array) !== 'undefined' && (function checkArrayBufferView() {
try {
return new Blob([new Uint8Array(100)]).size === 100;
} catch (error) {
return false;
}
}());
const hasToBlobSupport = (typeof HTMLCanvasElement !== 'undefined' ? HTMLCanvasElement.prototype.toBlob : false);
const hasBlobSupport = (hasToBlobSupport || (typeof Uint8Array !== 'undefined' && typeof ArrayBuffer !== 'undefined' && typeof atob !== 'undefined'));
const hasReaderSupport = (typeof FileReader !== 'undefined' || typeof URL !== 'undefined');
const hasCanvasSupport = (typeof HTMLCanvasElement !== 'undefined');
export default class ImageTools {
constructor() {
this.browserSupport = this.isSupportedByBrowser();
}
isSupportedByBrowser = () => (hasCanvasSupport && hasBlobSupport && hasReaderSupport);
resize = (file, maxDimensions) => new Promise((resolve) => {
if (!this.browserSupport || !file.type.match(/image.*/)) return resolve(file); // early exit - not supported
if (file.type.match(/image\/gif/)) return resolve(file); // early exit - could be an animated gif
const image = document.createElement('img');
image.onload = () => {
let width = image.width;
let height = image.height;
if (width >= height && width > maxDimensions.width) {
height *= maxDimensions.width / width;
width = maxDimensions.width;
} else if (height > maxDimensions.height) {
width *= maxDimensions.height / height;
height = maxDimensions.height;
} else return resolve(file); // early exit; no need to resize
EXIF.getData(image, () => {
const orientation = EXIF.getTag(image, 'Orientation');
const imageCanvas = this.drawImageToCanvas(image, orientation, 0, 0, width, height, 'contain');
if (hasToBlobSupport) imageCanvas.toBlob(blob => resolve(blob), file.type);
else resolve(this.toBlob(imageCanvas, file.type));
});
};
this.loadImage(image, file);
return true;
});
crop = (file, dimensions) => new Promise((resolve) => {
if (!this.browserSupport || !file.type.match(/image.*/)) return resolve(file); // early exit - not supported
if (file.type.match(/image\/gif/)) return resolve(file); // early exit - could be an animated gif
const image = document.createElement('img');
image.onload = () => {
if (dimensions.width > image.width && dimensions.height > image.height) return resolve(file); // early exit - no need to resize
const width = Math.min(dimensions.width, image.width);
const height = Math.min(dimensions.height, image.height);
if (image.width > dimensions.width * 2 || image.height > dimensions.height * 2) {
return this.resize(file, { width: dimensions.width * 2, height: dimensions.height * 2 }).then((zoomedOutImage) => {
this.crop(zoomedOutImage, { width, height }).then(resolve);
});
}
EXIF.getData(image, () => {
const orientation = EXIF.getTag(image, 'Orientation');
const imageCanvas = this.drawImageToCanvas(image, orientation, 0, 0, width, height, 'crop');
if (hasToBlobSupport) imageCanvas.toBlob(blob => resolve(blob), file.type);
else resolve(this.toBlob(imageCanvas, file.type));
});
};
this.loadImage(image, file);
return true;
});
drawImageToCanvas = (img, orientation = 1, x = 0, y = 0, width = img.width, height = img.height, method = 'contain') => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
ctx.save();
switch (Number(orientation)) {
// explained here: https://i.stack.imgur.com/6cJTP.gif
case 1:
break;
case 2:
ctx.translate(width, 0);
ctx.scale(-1, 1);
break;
case 3:
ctx.translate(width, height);
ctx.rotate((180 / 180) * Math.PI); // 180/180 is 1? No shit, but how else will you know its need 180 rotation?
break;
case 4:
ctx.translate(0, height);
ctx.scale(1, -1);
break;
case 5:
canvas.width = height;
canvas.height = width;
ctx.rotate((90 / 180) * Math.PI);
ctx.scale(1, -1);
break;
case 6:
canvas.width = height;
canvas.height = width;
ctx.rotate((90 / 180) * Math.PI);
ctx.translate(0, -height);
break;
case 7:
canvas.width = height;
canvas.height = width;
ctx.rotate((270 / 180) * Math.PI);
ctx.translate(-width, height);
ctx.scale(1, -1);
break;
case 8:
canvas.width = height;
canvas.height = width;
ctx.translate(0, width);
ctx.rotate((270 / 180) * Math.PI);
break;
default:
break;
}
if (method === 'crop') ctx.drawImage(img, (img.width / 2) - (width / 2), (img.height / 2) - (height / 2), width, height, 0, 0, width, height);
else ctx.drawImage(img, x, y, width, height);
ctx.restore();
return canvas;
};
toBlob = (canvas, type) => {
const dataURI = canvas.toDataURL(type);
const dataURIParts = dataURI.split(',');
let byteString;
if (dataURIParts[0].indexOf('base64') >= 0) {
byteString = atob(dataURIParts[1]);
} else {
byteString = decodeURIComponent(dataURIParts[1]);
}
const arrayBuffer = new ArrayBuffer(byteString.length);
const intArray = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i += 1) {
intArray[i] = byteString.charCodeAt(i);
}
const mimeString = dataURIParts[0].split(':')[1].split(';')[0];
let blob = null;
if (hasBlobConstructor) {
blob = new Blob([hasArrayBufferViewSupport ? intArray : arrayBuffer], { type: mimeString });
} else {
const bb = new BlobBuilder();
bb.append(arrayBuffer);
blob = bb.getBlob(mimeString);
}
return blob;
};
loadImage = (image, file) => {
if (typeof (URL) === 'undefined') {
const reader = new FileReader();
reader.onload = (event) => {
image.src = event.target.result;
};
reader.readAsDataURL(file);
} else {
image.src = URL.createObjectURL(file);
}
};
}
我制作了自己的版本,但没有实际使用文件阅读器。相反,我使用大多数现代浏览器都支持的
createObjectUrl
。
/**
* Function scaling an image from a file input to specified dimensions
* If the specified dimensions are not proportional to image dimensions the output image will be cropped at center
*
* @param file {File} Input file of a form
* @param dimensions {{width: number, height: number}} Dimenstions of the output image
* @returns {Promise<Blob | null>} Promise resolving to a scale image or a null if provided an invalid file type
*/
export async function scaleImageBeforeUpload(file: File, dimensions: {width: number, height: number}): Promise<Blob | null> {
// ensure the file is an image
if (!file.type.match(/image.*/)) return null;
const image = new Image();
image.src = URL.createObjectURL(file);
await new Promise<Event>((res) => image.onload = res);
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d", {alpha: true});
canvas.width = dimensions.width;
canvas.height = dimensions.height;
if (image.height <= image.width) {
const scaleProportions = canvas.height / image.height;
const scaledWidth = scaleProportions * image.width;
context.drawImage(image, (canvas.width - scaledWidth)/2, 0, scaledWidth, canvas.height);
}
else {
const scaleProportions = canvas.width / image.width;
const scaledHeight = scaleProportions * image.height;
context.drawImage(image, 0, (canvas.height - scaledHeight)/2, canvas.width, scaledHeight);
}
return new Promise((res) => canvas.toBlob(res));
}
世界上最低效的调整图像大小以适应最大文件大小的方法
function processImage(base64) {
return new Promise(function(resolve, reject) {
var img = new Image();
img.onload = function() {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var width = img.width;
var height = img.height;
var resizedBase64 = null;
while (resizedBase64 == null) {
console.log("width: " + width + " height: " + height);
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
if (canvas.toDataURL('image/png').length > maxFileSize) {
width = width * 0.9;
height = height * 0.9;
} else {
resizedBase64 = canvas.toDataURL('image/png')
}
}
console.log("width: " + width + " height: " + height);
resolve(resizedBase64);
};
img.src = base64;
});
}