如何在浏览器中通过 Javascript 压缩图像?
Posted
技术标签:
【中文标题】如何在浏览器中通过 Javascript 压缩图像?【英文标题】:How to compress an image via Javascript in the browser? 【发布时间】:2013-01-18 07:50:12 【问题描述】:TL;DR;
有没有办法在上传之前直接在浏览器端压缩图像(主要是 jpeg、png 和 gif)?我很确定 javascript 可以做到这一点,但我找不到实现它的方法。
这是我想要实现的完整场景:
用户访问我的网站,并通过input type="file"
元素选择一张图片,
此图像是通过 JavaScript 检索到的,我们会进行一些验证,例如文件格式正确、文件最大大小等,
如果一切正常,页面上会显示图像的预览,
用户可以进行一些基本操作,例如将图像旋转 90°/-90°、按照预定义的比例进行裁剪等,或者用户可以上传另一张图像并返回步骤 1,
当用户满意时,编辑后的图像会被压缩并“保存”到本地(不是保存到文件中,而是保存在浏览器内存/页面中),-
用户用姓名、年龄等数据填写表单
用户点击“完成”按钮,然后将包含数据+压缩图像的表单发送到服务器(不带AJAX),
到最后一步的整个过程应该在客户端完成,并且应该兼容最新的 Chrome 和 Firefox、Safari 5+ 和 IE 8+。如果可能,应该只使用 JavaScript(但我很确定这是不可能的)。
我现在还没有编写任何代码,但我已经考虑过了。通过File API可以在本地读取文件,可以使用Canvas元素进行图像预览和编辑,但是我找不到图像压缩部分的方法。
根据html5please.com 和caniuse.com 的说法,支持这些浏览器非常困难(感谢IE),但可以使用诸如FlashCanvas 和FileReader 之类的polyfill 来完成。
实际上,目标是减小文件大小,因此我将图像压缩视为一种解决方案。但是,我知道上传的图片每次都会在同一个地方显示在我的网站上,而且我知道这个显示区域的尺寸(例如 200x400)。因此,我可以调整图像大小以适应这些尺寸,从而减小文件大小。我不知道这种技术的压缩比是多少。
你怎么看?你有什么建议可以告诉我吗?您知道在 JavaScript 中压缩图像浏览器端的任何方法吗?感谢您的回复。
【问题讨论】:
【参考方案1】:简而言之:
使用带有 .readAsArrayBuffer 的 HTML5 FileReader API 读取文件 使用文件数据创建一个 Blob 并使用 window.URL.createObjectURL(blob) 获取其 url 创建新的 Image 元素并将其 src 设置为文件 blob url 将图像发送到画布。画布大小设置为所需的输出大小 通过 canvas.toDataURL("image/jpeg",0.7) 从画布取回按比例缩小的数据(设置您自己的输出格式和质量) 将新的隐藏输入附加到原始表单并将 dataURI 图像基本上作为普通文本传输 在后端,读取 dataURI,从 Base64 解码并保存来源:code。
【讨论】:
@NicholasKyriakides 我可以确认canvas.toDataURL("image/jpeg",0.7)
有效地压缩了它,它以质量 70 保存 JPEG(而不是默认质量 100)。
@Nicholas Kyriakides,这不是很好的区分。大多数编解码器都不是无损的,因此它们适合您的“缩减”定义(即您不能恢复到 100)。
缩小是指在高度和宽度方面使图像尺寸更小。这真的是压缩。这是有损压缩,但肯定是压缩。它并没有缩小像素,它只是将一些像素微调为相同的颜色,以便压缩可以以更少的位击中这些颜色。 JPEG 无论如何都内置了像素压缩,但在有损模式下,它表示可以将一些颜色关闭可以称为相同颜色。那仍然是压缩。缩小图形通常是指实际尺寸的变化。
我只想说:文件可以直接转到URL.createObjectUrl()
,而不需要把文件变成blob;该文件被视为一个 blob。
如果有人需要,我已经为 Psychowood 的代码示例添加了一个演示器 jsfiddle:jsfiddle.net/Abeeee/0wxeugrt/9【参考方案2】:
我发现其他答案中缺少两件事:
canvas.toBlob
(如果可用)比 canvas.toDataURL
性能更高,而且也是异步的。
文件->图像->画布->文件转换丢失EXIF数据;尤其是现代手机/平板电脑通常设置的图像旋转数据。
以下脚本处理这两点:
// From https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob, needed for Safari:
if (!HTMLCanvasElement.prototype.toBlob)
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob',
value: function(callback, type, quality)
var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
len = binStr.length,
arr = new Uint8Array(len);
for (var i = 0; i < len; i++)
arr[i] = binStr.charCodeAt(i);
callback(new Blob([arr], type: type || 'image/png'));
);
window.URL = window.URL || window.webkitURL;
// Modified from https://***.com/a/32490603, cc by-sa 3.0
// -2 = not jpeg, -1 = no data, 1..8 = orientations
function getExifOrientation(file, callback)
// Suggestion from http://code.flickr.net/2012/06/01/parsing-exif-client-side-using-javascript-2/:
if (file.slice)
file = file.slice(0, 131072);
else if (file.webkitSlice)
file = file.webkitSlice(0, 131072);
var reader = new FileReader();
reader.onload = function(e)
var view = new DataView(e.target.result);
if (view.getUint16(0, false) != 0xFFD8)
callback(-2);
return;
var length = view.byteLength, offset = 2;
while (offset < length)
var marker = view.getUint16(offset, false);
offset += 2;
if (marker == 0xFFE1)
if (view.getUint32(offset += 2, false) != 0x45786966)
callback(-1);
return;
var little = view.getUint16(offset += 6, false) == 0x4949;
offset += view.getUint32(offset + 4, little);
var tags = view.getUint16(offset, little);
offset += 2;
for (var i = 0; i < tags; i++)
if (view.getUint16(offset + (i * 12), little) == 0x0112)
callback(view.getUint16(offset + (i * 12) + 8, little));
return;
else if ((marker & 0xFF00) != 0xFF00) break;
else offset += view.getUint16(offset, false);
callback(-1);
;
reader.readAsArrayBuffer(file);
// Derived from https://***.com/a/40867559, cc by-sa
function imgToCanvasWithOrientation(img, rawWidth, rawHeight, orientation)
var canvas = document.createElement('canvas');
if (orientation > 4)
canvas.width = rawHeight;
canvas.height = rawWidth;
else
canvas.width = rawWidth;
canvas.height = rawHeight;
if (orientation > 1)
console.log("EXIF orientation = " + orientation + ", rotating picture");
var ctx = canvas.getContext('2d');
switch (orientation)
case 2: ctx.transform(-1, 0, 0, 1, rawWidth, 0); break;
case 3: ctx.transform(-1, 0, 0, -1, rawWidth, rawHeight); break;
case 4: ctx.transform(1, 0, 0, -1, 0, rawHeight); break;
case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
case 6: ctx.transform(0, 1, -1, 0, rawHeight, 0); break;
case 7: ctx.transform(0, -1, -1, 0, rawHeight, rawWidth); break;
case 8: ctx.transform(0, -1, 1, 0, 0, rawWidth); break;
ctx.drawImage(img, 0, 0, rawWidth, rawHeight);
return canvas;
function reduceFileSize(file, acceptFileSize, maxWidth, maxHeight, quality, callback)
if (file.size <= acceptFileSize)
callback(file);
return;
var img = new Image();
img.onerror = function()
URL.revokeObjectURL(this.src);
callback(file);
;
img.onload = function()
URL.revokeObjectURL(this.src);
getExifOrientation(file, function(orientation)
var w = img.width, h = img.height;
var scale = (orientation > 4 ?
Math.min(maxHeight / w, maxWidth / h, 1) :
Math.min(maxWidth / w, maxHeight / h, 1));
h = Math.round(h * scale);
w = Math.round(w * scale);
var canvas = imgToCanvasWithOrientation(img, w, h, orientation);
canvas.toBlob(function(blob)
console.log("Resized image to " + w + "x" + h + ", " + (blob.size >> 10) + "kB");
callback(blob);
, 'image/jpeg', quality);
);
;
img.src = URL.createObjectURL(file);
示例用法:
inputfile.onchange = function()
// If file size > 500kB, resize such that width <= 1000, quality = 0.9
reduceFileSize(this.files[0], 500*1024, 1000, Infinity, 0.9, blob =>
let body = new FormData();
body.set('file', blob, blob.name || "file.jpg");
fetch('/upload-image', method: 'POST', body).then(...);
);
;
【讨论】:
ToBlob 为我做了诀窍,创建了一个文件并在服务器上的 $_FILES 数组中接收。谢谢! 看起来很棒!但这适用于所有浏览器、网络和移动设备吗? (让我们忽略 IE)【参考方案3】:@PsychoWoods 的回答很好。我想提供我自己的解决方案。这个 Javascript 函数接受一个图像数据 URL 和一个宽度,将其缩放到新的宽度,并返回一个新的数据 URL。
// Take an image URL, downscale it to the given width, and return a new image URL.
function downscaleImage(dataUrl, newWidth, imageType, imageArguments)
"use strict";
var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl;
// Provide default values
imageType = imageType || "image/jpeg";
imageArguments = imageArguments || 0.7;
// Create a temporary image so that we can compute the height of the downscaled image.
image = new Image();
image.src = dataUrl;
oldWidth = image.width;
oldHeight = image.height;
newHeight = Math.floor(oldHeight / oldWidth * newWidth)
// Create a temporary canvas to draw the downscaled image on.
canvas = document.createElement("canvas");
canvas.width = newWidth;
canvas.height = newHeight;
// Draw the downscaled image on the canvas and return the new data URL.
ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, newWidth, newHeight);
newDataUrl = canvas.toDataURL(imageType, imageArguments);
return newDataUrl;
此代码可用于您拥有数据 URL 并希望为缩小图像提供数据 URL 的任何地方。
【讨论】:
拜托,你能给我详细介绍一下这个例子吗,如何调用函数以及如何返回结果? 这是一个示例:danielsadventure.info/Html/scaleimage.html 请务必阅读页面的源代码以了解其工作原理。 web.archive.org/web/20171226190510/danielsadventure.info/Html/… 对于其他想要阅读@DanielAllenLangdon 建议的链接的人 请注意,有时 image.width/height 会返回 0,因为它尚未加载。您可能需要将其转换为异步函数并收听 image.onload 以获取正确的图像和高度。【参考方案4】:你可以看看image-conversion,在这里试试 --> demo page
【讨论】:
请添加一些有关链接资源的信息 演示页面链接已损坏。你可以在这里测试:demo.wangyulue.com/image-conversion 这非常简单实用。伙计们,甚至不要花时间阅读其他答案... 它是迄今为止最好的插件,超级好用且功能强大。这可能是最好的答案。感谢分享【参考方案5】:@daniel-allen-langdon 在上面发布的downscaleImage()
函数存在问题,因为图像加载是异步,image.width
和 image.height
属性无法立即使用.
请参阅下面更新的 TypeScript 示例,该示例考虑到这一点,使用 async
函数,并根据最长尺寸而不是宽度调整图像大小
function getImage(dataUrl: string): Promise<HTMLImageElement>
return new Promise((resolve, reject) =>
const image = new Image();
image.src = dataUrl;
image.onload = () =>
resolve(image);
;
image.onerror = (el: any, err: ErrorEvent) =>
reject(err.error);
;
);
export async function downscaleImage(
dataUrl: string,
imageType: string, // e.g. 'image/jpeg'
resolution: number, // max width/height in pixels
quality: number // e.g. 0.9 = 90% quality
): Promise<string>
// Create a temporary image so that we can compute the height of the image.
const image = await getImage(dataUrl);
const oldWidth = image.naturalWidth;
const oldHeight = image.naturalHeight;
console.log('dims', oldWidth, oldHeight);
const longestDimension = oldWidth > oldHeight ? 'width' : 'height';
const currentRes = longestDimension == 'width' ? oldWidth : oldHeight;
console.log('longest dim', longestDimension, currentRes);
if (currentRes > resolution)
console.log('need to resize...');
// Calculate new dimensions
const newSize = longestDimension == 'width'
? Math.floor(oldHeight / oldWidth * resolution)
: Math.floor(oldWidth / oldHeight * resolution);
const newWidth = longestDimension == 'width' ? resolution : newSize;
const newHeight = longestDimension == 'height' ? resolution : newSize;
console.log('new width / height', newWidth, newHeight);
// Create a temporary canvas to draw the downscaled image on.
const canvas = document.createElement('canvas');
canvas.width = newWidth;
canvas.height = newHeight;
// Draw the downscaled image on the canvas and return the new data URL.
const ctx = canvas.getContext('2d')!;
ctx.drawImage(image, 0, 0, newWidth, newHeight);
const newDataUrl = canvas.toDataURL(imageType, quality);
return newDataUrl;
else
return dataUrl;
【讨论】:
我会添加quality
、resolution
和imageType
的解释(这个格式)【参考方案6】:
我发现与接受的答案相比,有更简单的解决方案。
使用 HTML5 FileReader API 和使用文件数据创建一个 Blob 并使用.readAsArrayBuffer
读取文件window.URL.createObjectURL(blob)
获取其 url 创建新的 Image 元素并将其 src 设置为文件 blob url 将图像发送到画布。画布大小设置为所需的输出大小 通过canvas.toDataURL("image/jpeg",0.7)
从画布中取回按比例缩小的数据(设置您自己的输出格式和质量)将新的隐藏输入附加到原始表单并将 dataURI 图像基本上作为普通文本传输在后端,读取 dataURI,从 Base64 解码,然后保存
根据你的问题:
有没有办法压缩图像(主要是 jpeg、png 和 gif) 直接浏览器端,在上传之前
我的解决方案:
使用URL.createObjectURL(inputFileElement.files[0])
直接用文件创建一个blob。
与接受的答案相同。
与接受的答案相同。值得一提的是,画布大小是必需的,使用img.width
和img.height
设置canvas.width
和canvas.height
。不是img.clientWidth
。
通过canvas.toBlob(callbackfunction(blob), 'image/jpeg', 0.5)
获取缩小图。设置'image/jpg'
无效。还支持image/png
。在callbackfunction
body 中使用let compressedImageBlob = new File([blob])
创建一个新的File
对象。
添加新的隐藏输入或send via javascript。服务器不需要解码任何东西。
查看https://javascript.info/binary 了解所有信息。看完这一章我想出了解决办法。
代码:
<!DOCTYPE html>
<html>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
Select image to upload:
<input type="file" name="fileToUpload" id="fileToUpload" multiple>
<input type="submit" value="Upload Image" name="submit">
</form>
</body>
</html>
这段代码看起来远没有其他答案那么可怕..
更新:
必须将所有内容都放入img.onload
。否则canvas
将无法在分配canvas
的时间正确地获取图像的宽度和高度。
function upload()
var f = fileToUpload.files[0];
var fileName = f.name.split('.')[0];
var img = new Image();
img.src = URL.createObjectURL(f);
img.onload = function()
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
canvas.toBlob(function(blob)
console.info(blob.size);
var f2 = new File([blob], fileName + ".jpeg");
var xhr = new XMLHttpRequest();
var form = new FormData();
form.append("fileToUpload", f2);
xhr.open("POST", "upload.php");
xhr.send(form);
, 'image/jpeg', 0.5);
3.4MB
.png
设置了image/jpeg
参数的文件压缩测试。
|0.9| 777KB |
|0.8| 383KB |
|0.7| 301KB |
|0.6| 251KB |
|0.5| 219kB |
【讨论】:
我喜欢您的解决方案,但是我们如何保存 EXIF 数据?【参考方案7】:编辑:根据 Mr Me 对此答案的评论,看起来压缩现在可用于 JPG/WebP 格式(请参阅https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL)。
据我所知,您不能使用画布压缩图像,而是可以调整其大小。使用 canvas.toDataURL 不会让您选择要使用的压缩比。你可以看看 canimage 完全符合你的要求:https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js
事实上,通常只调整图像大小以减小其大小就足够了,但如果你想更进一步,你将不得不使用新引入的方法 file.readAsArrayBuffer 来获取包含图像数据的缓冲区。
然后,只需使用 DataView 根据图像格式规范(http://en.wikipedia.org/wiki/JPEG 或 http://en.wikipedia.org/wiki/Portable_Network_Graphics)读取其内容。
处理图像数据压缩会很困难,但更糟糕的是尝试一下。另一方面,您可以尝试删除 PNG 标头或 JPEG exif 数据以使您的图像更小,这样做应该更容易。
您必须在另一个缓冲区上创建另一个 DataWiew 并用过滤后的图像内容填充它。然后,您只需使用 window.btoa 将您的图像内容编码为 DataURI。
如果你实现了类似的东西,请告诉我,看看代码会很有趣。
【讨论】:
也许自从您发布此内容后发生了一些变化,但您提到的 canvas.toDataURL 函数的第二个参数是您要应用的压缩量。【参考方案8】:试试这个可定制的纯 JS 示例 - 压缩率超过 90%:
<div id="root">
<p>Upload an image and see the result</p>
<input id="img-input" type="file" accept="image/*" style="display:block" />
</div>
<script>
const MAX_WIDTH = 320;
const MAX_HEIGHT = 180;
const MIME_TYPE = "image/jpeg";
const QUALITY = 0.7;
const input = document.getElementById("img-input");
input.onchange = function (ev)
const file = ev.target.files[0]; // get the file
const blobURL = URL.createObjectURL(file);
const img = new Image();
img.src = blobURL;
img.onerror = function ()
URL.revokeObjectURL(this.src);
// Handle the failure properly
console.log("Cannot load image");
;
img.onload = function ()
URL.revokeObjectURL(this.src);
const [newWidth, newHeight] = calculateSize(img, MAX_WIDTH, MAX_HEIGHT);
const canvas = document.createElement("canvas");
canvas.width = newWidth;
canvas.height = newHeight;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, newWidth, newHeight);
canvas.toBlob(
(blob) =>
// Handle the compressed image. es. upload or save in local state
displayInfo('Original file', file);
displayInfo('Compressed file', blob);
,
MIME_TYPE,
QUALITY
);
document.getElementById("root").append(canvas);
;
;
function calculateSize(img, maxWidth, maxHeight)
let width = img.width;
let height = img.height;
// calculate the width and height, constraining the proportions
if (width > height)
if (width > maxWidth)
height = Math.round((height * maxWidth) / width);
width = maxWidth;
else
if (height > maxHeight)
width = Math.round((width * maxHeight) / height);
height = maxHeight;
return [width, height];
// Utility functions for demo purpose
function displayInfo(label, file)
const p = document.createElement('p');
p.innerText = `$label - $readableBytes(file.size)`;
document.getElementById('root').append(p);
function readableBytes(bytes)
const i = Math.floor(Math.log(bytes) / Math.log(1024)),
sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
</script>
【讨论】:
迄今为止最好的答案。代码也干净【参考方案9】:我改进了一个头的功能是这样的:
var minifyImg = function(dataUrl,newWidth,imageType="image/jpeg",resolve,imageArguments=0.7)
var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl;
(new Promise(function(resolve)
image = new Image(); image.src = dataUrl;
log(image);
resolve('Done : ');
)).then((d)=>
oldWidth = image.width; oldHeight = image.height;
log([oldWidth,oldHeight]);
newHeight = Math.floor(oldHeight / oldWidth * newWidth);
log(d+' '+newHeight);
canvas = document.createElement("canvas");
canvas.width = newWidth; canvas.height = newHeight;
log(canvas);
ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, newWidth, newHeight);
//log(ctx);
newDataUrl = canvas.toDataURL(imageType, imageArguments);
resolve(newDataUrl);
);
;
它的用途:
minifyImg(<--DATAURL_HERE-->,<--new width-->,<--type like image/jpeg-->,(data)=>
console.log(data); // the new DATAURL
);
享受 ;)
【讨论】:
【参考方案10】:Compressor.js
https://github.com/fengyuanchen/compressorjs
import axios from 'axios';
import Compressor from 'compressorjs';
document.getElementById('file').addEventListener('change', (e) =>
const file = e.target.files[0];
if (!file)
return;
new Compressor(file,
quality: 0.6,
// The compression process is asynchronous,
// which means you have to access the `result` in the `success` hook function.
success(result)
const formData = new FormData();
// The third parameter is required for server
formData.append('file', result, result.name);
// Send the compressed image file to server with XMLHttpRequest.
axios.post('/path/to/upload', formData).then(() =>
console.log('Upload success');
);
,
error(err)
console.log(err.message);
,
);
);
【讨论】:
【参考方案11】:我使用了以下软件包: https://www.npmjs.com/package/browser-image-compression
npm install browser-image-compression
or
yarn add browser-image-compression
然后按照文档进行操作:
import imageCompression from 'browser-image-compression';
const options =
maxSizeMB: 0.5, // pretty much self-explanatory
maxWidthOrHeight: 500, // apparently px
imageCompression(file, options)
.then(function(compressedFile)
console.log(
"compressedFile instanceof Blob",
compressedFile instanceof Blob
); // true
console.log(
`compressedFile size $compressedFile.size /
1024 /
1024 MB`
); // smaller than maxSizeMB
return uploader(compressedFile); // code to actual upload, in my case uploader() is a function to upload to Firebase storage.
)
如果您对uploader()
感兴趣,下面是它的代码:
import initializeApp from "firebase/app";
const firebaseConfig =
// your config
;
initializeApp(firebaseConfig);
import getStorage, ref, uploadBytes, getDownloadURL from "firebase/storage";
const storage = getStorage();
const sRef = ref(storage);
const uploader = async (file) =>
/* uploads to root */
// const imageRef = ref(sRef, file.name);
// console.log(imageRef);
// await uploadBytes(imageRef, file).then((snapshot) =>
// console.log("Uploaded a blob or file!", snapshot);
// );
/* upload to folder 'techs/' */
const folderRef = ref(sRef, "techs/" + file.name);
await uploadBytes(folderRef, file);
// get URL
const url = await getDownloadURL(ref(storage, folderRef));
console.log("url: ", url);
return url;
;
【讨论】:
【参考方案12】:对于 JPG 图像压缩,您可以使用称为 JIC 的最佳压缩技术 (Javascript图片压缩)这对你一定有帮助-->https://github.com/brunobar79/J-I-C
【讨论】:
不降低质量以上是关于如何在浏览器中通过 Javascript 压缩图像?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Javascript 在浏览器中通过 X-Y 坐标定位?
在 xul 和 javascript 中通过按钮单击事件上的另一个工具提示文本更改工具提示文本