對於大尺寸圖片的上傳,在前端進行壓縮除了省流量外,最大的意義是極大的提高了用戶體驗。
這種體驗包括兩方面:
1、由於上傳圖片尺寸比較小,因此上傳速度會比較快,交互會更加流暢,同時大大降低了網絡異常導致上傳失敗風險。
2、最重要的體驗改進點:省略了圖片的再加工成本。很多網站的圖片上傳功能都會對圖片的大小進行限制,尤其是頭像上傳,限制5M或者2M以內是非常常見的。然后現在的數碼設備拍攝功能都非常出眾,一張原始圖片超過2M幾乎是標配,此時如果用戶想把手機或相機中的某個得意圖片上傳作為自己的頭像,就會遇到因為圖片大小限制而不能上傳的窘境,不得不對圖片進行再處理,而這種體驗其實非常不好的。如果可以在前端進行壓縮,則理論上對圖片尺寸的限制是沒有必要的。
HTML5 file API加canvas實現圖片前端JS壓縮
要想使用JS實現圖片的壓縮效果,原理其實很簡單,核心API就是使用canvas的drawImage()方法。
canvas的drawImage()方法API如下:
context.drawImage(img, dx, dy);
context.drawImage(img, dx, dy, dWidth, dHeight);
context.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
后面最復雜的語法雖然看上去有9大參數,但不用慌,實際上可以看出就3個參數:
- img
- 就是圖片對象,可以是頁面上獲取的DOM對象,也可以是虛擬DOM中的圖片對象。
- dx, dy, dWidth, dHeight
-
表示在
canvas畫布上規划處一片區域用來放置圖片,dx, dy為canvas元素的左上角坐標,dWidth, dHeight指canvas元素上用在顯示圖片的區域大小。如果沒有指定sx,sy,sWidth,sHeight這4個參數,則圖片會被拉伸或縮放在這片區域內。 - sx,sy,swidth,sheight
-
這4個坐標是針對圖片元素的,表示圖片在
canvas畫布上顯示的大小和位置。sx,sy表示圖片上sx,sy這個坐標作為左上角,然后往右下角的swidth,sheight尺寸范圍圖片作為最終在canvas上顯示的圖片內容。
drawImage()方法有一個非常怪異的地方,大家一定要注意,那就是5參數和9參數里面參數位置是不一樣的,這個和一般的API有所不同。一般API可選參數是放在后面。但是,這里的drawImage()9個參數時候,可選參數sx,sy,swidth,sheight是在前面的。如果不注意這一點,有些表現會讓你無法理解。
下圖為MDN上原理示意:

對於本文的圖片壓縮,需要用的是是5個參數語法。舉個例子,一張圖片(假設圖片對象是img)的原始尺寸是4000*3000,現在需要把尺寸限制為400*300大小,很簡單,原理如下代碼示意:
var canvas = document.createElement('canvas'); var context = canvas.getContext('2d'); canvas.width = 400; canvas.height = 300; // 核心JS就這個 context.drawImage(img,0,0,400,300);
把一張大的圖片,直接畫在一張小小的畫布上。此時大圖片就天然變成了小圖片,壓縮就這么實現了,是不是簡單的有點超乎想象。
當然,若要落地於實際開發,我們還需要做些其他的工作,就是要解決圖片來源和圖片去向的問題。
1、如何把系統中圖片呈現在瀏覽器中?
HTML5 file API可以讓圖片在上傳之前直接在瀏覽器中顯示,通常使用FileReader方法,代碼示意如下:
var reader = new FileReader(), img = new Image(); // 讀文件成功的回調 reader.onload = function(e) { // e.target.result就是圖片的base64地址信息 img.src = e.target.result; }; eleFile.addEventListener('change', function (event) { reader.readAsDataURL(event.target.files[0]); });
於是,包含圖片信息的context.drawImage()方法中的img圖片就有了。
2、如何把canvas畫布轉換成img圖像
canvas天然提供了2個轉圖片的方法,一個是:
canvas.toDataURL()方法
canvas.toDataURL(mimeType, qualityArgument)
-
可以把圖片轉換成base64格式信息,純字符的圖片表示法。
其中:
mimeType表示canvas導出來的base64圖片的類型,默認是png格式,也即是默認值是'image/png',我們也可以指定為jpg格式'image/jpeg'或者webp等格式。file對象中的file.type就是文件的mimeType類型,在轉換時候正好可以直接拿來用(如果有file對象)。qualityArgument表示導出的圖片質量,只要導出為jpg和webp格式的時候此參數才有效果,默認值是0.92,是一個比較合理的圖片質量輸出參數,通常情況下,我們無需再設定。 - canvas.toBlob()方法
canvas.toBlob(callback, mimeType, qualityArgument)
可以把canvas轉換成Blob文件,通常用在文件上傳中,因為是二進制的,對后端更加友好。
和toDataURL()方法相比,toBlob()方法是異步的,因此多了個callback參數,這個callback回調方法默認的第一個參數就是轉換好的blob文件信息。
將canvas圖片轉換成二進制的blob文件,然后再ajax上傳的,代碼如下:
// canvas轉為blob並上傳
canvas.toBlob(function (blob) {
// 圖片ajax上傳
var xhr = new XMLHttpRequest();
// 開始上傳
xhr.open("POST", 'upload.php', true);
xhr.send(blob);
});
於是,經過“圖片→canvas壓縮→圖片”三步曲,我們完成了圖片前端壓縮並上傳的功能。
//HTML代碼:
<input id="file" type="file">
//JS代碼:
var eleFile = document.querySelector('#file');
// 壓縮圖片需要的一些元素和對象
var reader = new FileReader(), img = new Image();
// 選擇的文件對象
var file = null;
// 縮放圖片需要的canvas
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
// base64地址圖片加載完畢后
img.onload = function () {
// 圖片原始尺寸
var originWidth = this.width;
var originHeight = this.height;
// 最大尺寸限制
var maxWidth = 400, maxHeight = 400;
// 目標尺寸
var targetWidth = originWidth, targetHeight = originHeight;
// 圖片尺寸超過400x400的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更寬,按照寬度限定尺寸
targetWidth = maxWidth;
targetHeight = Math.round(maxWidth * (originHeight / originWidth));
} else {
targetHeight = maxHeight;
targetWidth = Math.round(maxHeight * (originWidth / originHeight));
}
}
// canvas對圖片進行縮放
canvas.width = targetWidth;
canvas.height = targetHeight;
// 清除畫布
context.clearRect(0, 0, targetWidth, targetHeight);
// 圖片壓縮
context.drawImage(img, 0, 0, targetWidth, targetHeight);
// canvas轉為blob並上傳
canvas.toBlob(function (blob) {
// 圖片ajax上傳
var xhr = new XMLHttpRequest();
// 文件上傳成功
xhr.onreadystatechange = function() {
if (xhr.status == 200) {
// xhr.responseText就是返回的數據
}
};
// 開始上傳
xhr.open("POST", 'upload.php', true);
xhr.send(blob);
}, file.type || 'image/png');
};
// 文件base64化,以便獲知圖片原始尺寸
reader.onload = function(e) {
img.src = e.target.result;
};
eleFile.addEventListener('change', function (event) {
file = event.target.files[0];
// 選擇的文件是圖片
if (file.type.indexOf("image") == 0) {
reader.readAsDataURL(file);
}
});
上傳前還可以預覽:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <script type="text/javascript" src="jquery-1.11.3.min.js"></script> <script language="javascript"> window.onload=function(){ var eleFile = document.querySelector('#jjfxSoft_iconPath'); // 壓縮圖片需要的一些元素和對象 var reader = new FileReader(), img = new Image(); // 選擇的文件對象 var file = null; // 縮放圖片需要的canvas var canvas = document.createElement('canvas'); var context = canvas.getContext('2d'); // base64地址圖片加載完畢后 img.onload = function () { debugger // 圖片原始尺寸 var originWidth = this.width; var originHeight = this.height; // 最大尺寸限制 var maxWidth = 300, maxHeight = 300; // 目標尺寸 var targetWidth = originWidth, targetHeight = originHeight; // 圖片尺寸超過300x300的限制 if (originWidth > maxWidth || originHeight > maxHeight) { if (originWidth / originHeight > maxWidth / maxHeight) { targetWidth = maxWidth; targetHeight = Math.round(maxWidth * (originHeight / originWidth)); } else { targetHeight = maxHeight; targetWidth = Math.round(maxHeight * (originWidth / originHeight)); } } // canvas對圖片進行縮放 canvas.width = targetWidth; canvas.height = targetHeight; // 清除畫布 context.clearRect(0, 0, targetWidth, targetHeight); // 圖片壓縮 context.drawImage(img, 0, 0, targetWidth, targetHeight); var type = 'image/jpeg'; //將canvas元素中的圖像轉變為DataURL var dataurl = canvas.toDataURL(type); $("#ceshi1").attr("src",dataurl); //抽取DataURL中的數據部分,從Base64格式轉換為二進制格式 var bin = atob(dataurl.split(',')[1]); //創建空的Uint8Array var buffer = new Uint8Array(bin.length); //將圖像數據逐字節放入Uint8Array中 for (var i = 0; i < bin.length; i++) { buffer[i] = bin.charCodeAt(i); } //利用Uint8Array創建Blob對象 var blob = new Blob([buffer.buffer], {type: type}); var url = window.URL.createObjectURL(blob); $("#ceshi").attr("src",url); }; // 文件base64化,以便獲知圖片原始尺寸 reader.onload = function(e) { img.src = e.target.result; }; eleFile.addEventListener('change', function (event) { file = event.target.files[0]; // 選擇的文件是圖片 if (file.type.indexOf("image") == 0) { reader.readAsDataURL(file); } }); } </script> </head> <body> <img id="ceshi"> <img id="ceshi1"> <input name="file" type="file" id="jjfxSoft_iconPath"> </body> </html>

