[英]How to compress an image via Javascript in the browser?
TL;博士;
有沒有辦法在上傳之前直接在瀏覽器端壓縮圖像(主要是 jpeg、png 和 gif)? 我很確定 JavaScript 可以做到這一點,但我找不到實現它的方法。
這是我要實現的完整場景:
input type="file"
元素選擇圖像,到最后一步的整個過程應該在客戶端完成,並且應該在最新的 Chrome 和 Firefox、Safari 5+ 和IE 8+上兼容。 如果可能的話,應該只使用 JavaScript (但我很確定這是不可能的)。
我現在還沒有編寫任何代碼,但我已經考慮過了。 通過文件 API可以在本地讀取文件,可以使用Canvas元素完成圖像預覽和編輯,但我找不到執行圖像壓縮部分的方法。
根據html5please.com和caniuse.com ,支持這些瀏覽器非常困難(感謝 IE),但可以使用諸如FlashCanvas和FileReader之類的 polyfill 來完成。
實際上,目標是減小文件大小,因此我將圖像壓縮視為一種解決方案。 但是,我知道上傳的圖片每次都會在同一個地方顯示在我的網站上,而且我知道這個顯示區域的尺寸(例如 200x400)。 因此,我可以調整圖像大小以適應這些尺寸,從而減小文件大小。 我不知道這種技術的壓縮比是多少。
你怎么看? 你有什么建議要告訴我嗎? 您知道在 JavaScript 中壓縮圖像瀏覽器端的任何方法嗎? 感謝您的回復。
簡而言之:
來源: 代碼。
我看到其他答案中缺少兩件事:
canvas.toBlob
(如果可用)比canvas.toDataURL
性能canvas.toDataURL
,而且是異步的。以下腳本處理這兩點:
// 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://stackoverflow.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://stackoverflow.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(...);
});
};
@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 的任何地方。
你可以看看image-conversion ,在這里試試 --> 演示頁面
我曾與一個問題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;
}
}
我發現與接受的答案相比,有更簡單的解決方案。
使用帶有.readAsArrayBuffer
的 HTML5 FileReader API 讀取文件- 使用文件數據創建一個 Blob 並使用
window.URL.createObjectURL(blob)
獲取其 url- 創建新的 Image 元素並將其 src 設置為文件 blob url
- 將圖像發送到畫布。 畫布大小設置為所需的輸出大小
通過從畫布canvas.toDataURL("image/jpeg",0.7)
縮小的數據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
。 使一個新的File
內的對象callbackfunction
體與let compressedImageBlob = new File([blob])
添加新的隱藏輸入或通過 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);
}
}
帶有image/jpeg
參數集的3.4MB
.png
文件壓縮測試。
|0.9| 777KB |
|0.8| 383KB |
|0.7| 301KB |
|0.6| 251KB |
|0.5| 219kB |
編輯:根據 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。
讓我知道你是否實現了類似的東西,通過代碼會很有趣。
您可以使用 HTML <canvas>
元素壓縮圖像:
function compressImage(imgToCompress, resizingFactor, quality) { // resizing the image const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); const originalWidth = imgToCompress.width; const originalHeight = imgToCompress.height; const canvasWidth = originalWidth * resizingFactor; const canvasHeight = originalHeight * resizingFactor; canvas.width = canvasWidth; canvas.height = canvasHeight; context.drawImage( imgToCompress, 0, 0, originalWidth * resizingFactor, originalHeight * resizingFactor ); // reducing the quality of the image canvas.toBlob( (blob) => { if (blob) { // showing the compressed image resizedImage.src = URL.createObjectURL(resizedImageBlob); } }, "image/jpeg", quality ); }
有關詳細說明,請參閱此博客文章: https://img.ly/blog/how-to-compress-an-image-before-uploading-it-in-javascript/
我改進了功能 a head 是這樣的:
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
});
請享用 ;)
壓縮器.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); }, }); });
對於 JPG 圖像壓縮,您可以使用稱為 JIC(Javascript 圖像壓縮)的最佳壓縮技術,這肯定會對您有所幫助 --> https://github.com/brunobar79/JIC
試試這個可定制的純 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>
我使用了以下包: 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;
};
對於現代瀏覽器,使用createImageBitmap()
而不是img.onload
async function compressImage(blobImg, percent) { let bitmap = await createImageBitmap(blobImg); let canvas = document.createElement("canvas"); let ctx = canvas.getContext("2d"); canvas.width = bitmap.width; canvas.height = bitmap.height; ctx.drawImage(bitmap, 0, 0); let dataUrl = canvas.toDataURL("image/jpeg", percent/100); return dataUrl; } inputImg.addEventListener('change', async(e) => { let img = e.target.files[0]; console.log('File Name: ', img.name) console.log('Original Size: ', img.size.toLocaleString()) let imgCompressed = await compressImage(img, 75) // set to 75% let compSize = atob(imgCompressed.split(",")[1]).length; console.log('Compressed Size: ', compSize.toLocaleString()) //console.log(imgCompressed) })
<input type="file" id="inputImg">
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.