爲何說這是一篇比較適合小白的前端圖片壓縮文章呢?由於我也是一個剛工做半年的前端小白,最近接到了一個前端圖片壓縮上傳的任務,經過各類百度博客完成了這項任務,可是任務完成後對各類技術細節卻還不是特別理解,因此我針對我不理解的每個技術細節都進行了記錄和學習,最後造成本篇博客,我以爲我不瞭解的地方可能也會有別的同窗不是很瞭解,因此本篇博客科普向比較重,技術深度可能達不到大佬們的眼界,但也是本身的記錄和學習,加油!!!javascript
若是您只是爲了copy代碼實現功能,建議您不要看這篇博客了。若是您是copy了代碼實現了功能,想回來瞭解具體的實現流程、實現原理以及部分科普,我以爲本篇博客能給你帶來不小的收穫。css
最近的項目有一個技術場景,簡單來講就是用戶須要上傳圖片至服務器。就是這麼一個簡單的技術場景,可是用戶是不可控的,他們可能上傳的圖片會是幾M甚至十幾M的大小,如此大的圖片必然會致使上傳時間過長,用戶感知很差,服務器壓力大等不良影響。html
做爲組裏惟一一個前端,老大就給了我一個任務,在用戶上傳圖片前把圖片壓縮了再上傳。那麼本篇博客主要講解的就是圖片壓縮中我是如何進行壓縮的,以後的博客會講解如何將壓縮的圖片進行上傳。前端
本文實現的功能流程以下:java
簡要流程圖以下:web
FileReader
?我知道
canvas
是畫布,可是具體是怎麼樣用它進行圖片壓縮的呢?Base64我知道可是什麼是
DataURL格式的數據呢?
Blob對象又是什麼?
同窗們,不着急,對於這些我都會進行一一講解,由於這也是做爲一個小白的我初次見到這些陌生名詞時的疑惑。canvas
FileReader
這個對象是作什麼的?在本次圖片壓縮中起到了什麼做用?後端
咱們先來看看MDN上對它的解釋:FileReader
對象容許Web應用程序異步讀取存儲在用戶計算機上的文件(或原始數據緩衝區)的內容,使用File
或 Blob
對象指定要讀取的文件或數據。api
通俗來說,就是這個對象是用來讀取File
對象或Blob
對象的。File
對象就是<input type="file">
獲取到的對象,而Blob
(二進制)對象在本文的第5點有講解。跨域
做爲一個js原生的用於讀取文件的對象,FileReader
自己就有較爲完整的鉤子函數以及一些實例方法,可是本文主要介紹圖片壓縮,因此在這裏只重點講本文使用到的1個鉤子函數和1個實例方法,對其它的鉤子和方法都不作詳細介紹。
FileReader.onload
:處理load
事件。即該鉤子在讀取操做完成時觸發,經過該鉤子函數能夠完成例如讀取完圖片後進行預覽的操做,或讀取完圖片後對圖片內容進行二次處理等操做。
FileReader.readAsDataURL
:讀取方法,而且讀取完成後,result
屬性將返回 Data URL
格式(Base64 編碼)的字符串,表明圖片內容。
除了用到的這個鉤子和這個實例方法外,FileReader
對象還有onabort
、onerror
、onloadstart
、onloadend
、onprogress
等鉤子;也有abort()
、readAsArrayBuffer
、readAsBinaryString
等實例方法,在次就不過多描述。
在onload
這個鉤子對上傳的圖片實現了預覽,而且進行了圖片壓縮處理。經過readAsDataURL()
方法進行了文件的讀取,而且經過result
屬性拿到了圖片的Base64(DataURL)
格式的數據,而後經過該數據實現了圖片預覽的功能。有的同窗看到這裏是否是有點好奇,爲何拿到了這個Base64(DataURL)
格式的數據就能直接展現處圖片了呢?沒關係,往下看,我會在後文中解釋這個DataURL
格式的神奇。
FileReader
部分代碼以下:
function canvasDataURL(file,item,callback) { //壓縮轉化爲base64
var reader = new FileReader(); //讀取文件的對象
reader.readAsDataURL(file); //對文件讀取,讀取完成後會將內容以base64的形式賦值給result屬性
reader.onload = function (e) { //讀取完成的鉤子
console.log("原始二進制字符串:",this.result.toString());
const img = new Image();
const quality = 0.2; // 圖像質量
const canvas = document.createElement('canvas');
const drawer = canvas.getContext('2d');
img.src = this.result;
console.log("FileReader對象:",this);
//圖片預覽
var picDom = $(item.item).find("img");
picDom.attr('src', this.result); //圖片連接(base64)
//圖片壓縮轉碼
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
drawer.drawImage(img, 0, 0, canvas.width, canvas.height);
convertBase64UrlToBlob(canvas.toDataURL(file.type, quality), callback);
}
}
}
複製代碼
canvas
元素衆所周知是畫布,那麼canvas
在圖片壓縮中起到了什麼做用?實現圖片壓縮的核心內容主要是使用到了canvas
的什麼方法?
實現圖片壓縮最核心的地方就在canvas
這裏,咱們先使用CanvasRenderingContext2D.drawImage()
方法將選中的圖片文件在畫布上繪製出來,再使用Canvas.toDataURL()
將畫布上的圖片信息轉換成base64(DataURL)格式的數據。有同窗會問,那麼是在哪兒實現的壓縮?其實壓縮的核心就在Canvas.toDataURL()
方法的quality
參數上了,下面咱們會具體介紹本文中使用到的2個canvas
畫布上的方法。
語法以下:
context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
複製代碼
這個方法是CanvasRenderingContext2D
上的繪製圖片的方法,若是有朋友想了解CanvasRenderingContext2D
和canvas
之間的聯繫和區別的話能夠自行了解,這裏不過多贅述。
首先介紹一下該方法所接受的9個參數:
image
:Object;繪製在Canvas上的元素,能夠是各種Canvas圖片資源),如圖片,SVG圖像,Canvas元素自己等。
dx
:Number;在Canvas畫布上規劃一片區域用來放置圖片,dx就是這片區域的左上角橫座標。
dy
:Nmuber;在Canvas畫布上規劃一片區域用來放置圖片,dy就是這片區域的左上角縱座標。
dWidth
:Number;在Canvas畫布上規劃一片區域用來放置圖片,dWidth就是這片區域的寬度。
dHeight
:Number;在在Canvas畫布上規劃一片區域用來放置圖片,dHeight就是這片區域的高度。
sx
:Number;表示圖片元素繪製在Canvas畫布上起始橫座標。
sy
:Number;表示圖片元素繪製在Canvas畫布上起始縱座標。
sWidth
:Number;表示圖片元素從座標點開始算,多大的寬度內容繪製Canvas畫布上。
sHeight
;表示圖片元素從座標點開始算,多大的高度內容繪製Canvas畫布上。
不少同窗看到這裏這麼多個參數是否是有點懵逼了?不用慌,我也很懵逼,其實我對canvas也不是很瞭解,不過不要緊咱們直接看看本文中使用到這個方法時咱們傳遞了什麼參數
//代碼
//先建立canvas畫布,再獲取canvas畫布上的2d繪圖環境,經過這個2d繪圖環境纔可以使用繪製API
const canvas = document.createElement('canvas');
//返回一個在畫布上繪製2d圖的環境對象,該對象上包含有canvas繪製2d圖形的API
const drawer = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
drawer.drawImage(img, 0, 0, canvas.width, canvas.height);
//實際傳遞的參數
drawer.drawImage(image, dx, dy, dWidth, dHeight)
複製代碼
結合上面列舉的9個參數,咱們能夠知道咱們實際上只使用到了5個參數。咱們傳遞了1個圖片,緊接着咱們定義了canvas上的起始區域座標點以及這個canvas上放置圖片的區域的寬高。
實質上,咱們設置的canvas放置圖片區域的寬高大小跟圖片自己是如出一轍的,由於在本文中,使用該方法的目的不在於在canvas上展現一張圖片給用戶看,而是在於在canvas上繪製出這張圖片,這樣咱們才能使用接下來的的這個Canvas.toDataURl()
方法
咚!咚!咚!敲重點了,這個方法纔是本文中實現圖片壓縮的核心。
語法以下:
canvas.toDataURL(mimeType, quality);
複製代碼
Canvas.toDataURl()
方法能夠將canvas畫布上的信息轉換爲base64(DataURL)格式的圖像信息,純字符的圖片表示形式。該方法接收2個參數:
mimeType
(可選):String;表示須要轉換的圖像的mimeType類型。默認值是image/png,還能夠是image/jpeg,甚至image/webp(前提瀏覽器支持)等。
quailty
(可選):Number;quality表示轉換的圖片質量。範圍是0到1。此參數要想有效,圖片的mimeType須要是image/jpeg或者image/webp,其餘mimeType值無效。默認壓縮質量是0.92。
到這裏,不少同窗就能夠知道了,前端圖片壓縮的核心方法,是否是就在這個方法的quailty
參數上面呢?好了,讓咱們看看本文使用到該方法的地方:
const canvas = document.createElement('canvas');
const quality = 0.2; //設置壓縮比例
canvas.toDataURL(file.type, quality)
複製代碼
能夠看到,本文中設置了壓縮質量爲0.2,須要注意的是否是說壓縮質量設置爲0.2實際壓縮效果就爲5倍壓縮(在本文中,壓縮質量設置成了0.2但實際壓縮效果確實7-9倍),簡單的說,當到達了這一步之後,其實圖片已經完成了壓縮,咱們已經能夠直接拿着返回的base64(DataURL)格式的數據去渲染圖片,可是,若是你的目的是將圖片先進行壓縮,壓縮後再上傳給服務器,而且服務器只接受二進制的圖片信息的話,那就得好好考慮怎麼將base64轉換成二進制Blob對象了,關於Blob,不要着急,我會在下一篇上傳篇中對它進行科普。
小tips:該方法爲同步方法,若是須要轉換的Canvas尺寸很大,則會阻塞腳本的運行,所以須要注意控制Canvas的尺寸。
語法以下:
canvas.toBlob(callback, mimeType, quality)
複製代碼
若是看了6.3小節的同窗應該會以爲,這2個方法長得差很少,參數也差很少,那麼它們的效果是否也是差很少的呢?
固然,該方法的做用是將canvas畫布上的信息轉換爲Blob對象。該方法接收的參數基本與6.3的方法相同,區別在於,該方法多接受一個參數,該參數爲:
callback
:Function;toBlob()方法執行成功後的回調方法,支持一個參數,表示當前轉換的Blob對象。
6.3小節的最後說過,toDataURL()
方法是同步方法,那麼咱們toBlob()
與此不一樣,它是一個異步的方法,因此該方法會多接受一個參數callback
,該參數就是toBlob()
的回調函數。
好了,既然本文最終目的是將file壓縮後,再轉換成Blob對象上傳至後端,那麼爲何不直接使用toBlob()
方法,而是使用toDataURL()
方法呢?對於這點的解釋我會在下一篇上傳篇中進行詳解,各位同窗能夠持續關注個人博客。
不知道有心的同窗發現了嗎?本文中屢次提到了DataURL
格式的數據,那究竟什麼纔是DataURL
格式的數據呢?
在下面對DataURL
展開了解以前,咱們能夠先來複習一下常見的img
標籤的src是什麼樣的?
那麼src除了這種賦值方式以外,還有什麼形式可以展現圖片嗎?
固然有,那就是咱們的DataURL
,詳見下圖:
DataURL
在實際中有什麼用處呢?它的定義是什麼?什麼場景下須要用到它?帶着這個疑問看下去吧。
先來看看我從網上找到的比較官方的定義:DataURL
是由RFC2397定義的一種把小文件直接嵌入文檔的方案。格式以下:
<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAS...">
複製代碼
咱們將例子與格式一一對照來看
MIME type
:表示數據呈現的格式,簡單來講就是這個數據的類型是什麼?是png仍是jpg甚至是html,結合實例來看,在實例中咱們的這段數據它的類型就是一個圖片,並且是個jpg格式的圖片。
character
:字符集;這個是可選項,默認爲charset=US-ASCII
,若是指定的是圖片的話,就再也不使用字符集了。在實例中,咱們的這段數據表明的就是一張圖片,隨意是沒有這個字段的。
base64
:這一部分將代表數據編碼方式,固然咱們能夠不使用base64的編碼方式,那樣咱們將使用標準的URL編碼方式。在本實例中,咱們的圖片就是採起base64的編碼方式。
enconded data
:這就是實際的數據了,在實例中,這就是這張圖片的base64編碼。
好了,DataURL
的定義講完了,那麼DataURL
只能在圖片裏使用嗎?固然不是,它能表示的東西有不少不少,好比大家能夠複製下面的這段代碼到瀏覽器地址欄中粘貼看一看
data:text/html;charset=UTF-8,<html><body><p>歡迎看劉偉C的博客</p></body></html>
複製代碼
下面會講下DataURL
的優缺點。
這裏就不作鋪墊直接說
優勢:
DataURL
,由於他不須要向外界發起訪問。DataURL
來講,它並不須要向服務器發送Http請求,它就天然不會佔用一個http會話資源。缺點:
base64
的數據體積實際上會比原數據大,也就是Data URL形式的圖片會比二進制格式的圖片體積大1/3。因此若是圖片較小,使用DataURL
形式的話是比較有利的,圖片較小即便比原圖片大一些也不會大不少,相比發起一個Http會話這點開銷算什麼。可是若是圖片較大的狀況下,使用DataURL
的開銷就會相應增大了,具體如何取捨還得各位同窗結合實際場景考量。那麼總結一下,DataURL
帶來的便利緣由就是一個:它不須要發起http請求;而它的缺點概括起來就是兩個:體積比原有還要大、不會被緩存。
看完上面的科普,同窗們是否是都瞭解了DataURL
的優缺點,那麼不知道各位是否會好奇,本次前端壓縮爲什麼使用的是DataURL
呢?
答案其實在第一小節中有,由於咱們想作圖片預覽,而圖片預覽是經過FileReader
對象實現的,FileReader
會去讀文件,而且返回文件的內容,可是返回的內容要麼就是二進制字符串,要麼就是二進制字節數組,這些都不能直接用於圖片展現,只有調用FileReader.readAsDataURL()
返回的DataURL
格式的數據能夠直接使用。
由於公司使用的前端框架比較非主流,是layUI框架的,因此個人代碼也是基於layUI的前端壓縮(因此大家直接copy個人代碼大機率是跑不起來的),不過核心思想核心方法核心原理在博客中都有體現,只要理解了思想,其實不難將它復刻到大家本身的項目中去。
代碼以下:
//這是layUI的文件上傳組件,不瞭解的朋友也不必去看文檔,理解思想就好了
var upload1 = upload.render({
elem: '.uploadImg',
url: releaseUrl,
accept: 'images',
auto: false,
//選擇文件後的鉤子函數
choose: function (obj) {
var that = this;
//預讀本地文件示例,不支持ie8
obj.preview(function (index, file, result) {
console.log("選中的文件:", file);
var index = layer.load(1, {
content: '圖片上傳中....',
shade: 0.2,
success: function (layer) {
layer.find('.layui-layer-content').css({
'paddingTop': '40px',
'width': '60px',
'textAlign': 'center',
'backgroundPositionX': 'center'
});
}
});
//這段開始纔是壓縮的重點,因此關注這段代碼便可
//若圖片超過1M則啓動壓縮
if (file.size > (1024 * 1024)) {
// console.log("大於1m");
canvasDataURL(file, that, function (blob) {
// console.log("壓縮後的二進制Blob對象:",blob);
console.log("壓縮前:" + (file.size / 1024 / 1024) + "M");
console.log("壓縮後:" + (blob.size / 1024 / 1024) + "M");
var compresFile = new File([blob], file.name, {
type: file.type
})
obj.upload(1, compresFile);
})
}
//若圖片不超過1M則無需壓縮,直接傳
else {
var picDom = $(that.item).find("img");
picDom.attr('src', result);
obj.upload(1, file);
}
});
},
})
//下面是壓縮部分的核心代碼
/** * 經過canvas畫布實現壓縮,並轉化爲base64格式的圖片 * @param {File} file : 圖片 * @param {Object} item :經過item找到當前對象的img標籤 * @param {Function} callback :回調函數 */
function canvasDataURL(file,item,callback) { //壓縮轉化爲base64
var reader = new FileReader(); //讀取文件的對象
reader.readAsDataURL(file); //對文件讀取,讀取完成後會將內容以base64的形式賦值給result屬性
reader.onload = function (e) { //讀取完成的鉤子
const img = new Image();
const quality = 0.2; // 圖像質量
//先建立canvas畫布,再獲取canvas畫布上的2d繪圖環境,經過這個2d繪圖環境纔可以使用繪製API
const canvas = document.createElement('canvas'); //建立canvas畫布
const drawer = canvas.getContext('2d'); //返回一個在畫布上繪製2d圖的環境對象,該對象上包含有canvas繪製2d圖形的API
img.src = this.result;
// console.log("FileReader對象:",this);
//圖片預覽
var picDom = $(item.item).find("img");
picDom.attr('src', this.result); //圖片連接(base64)
//圖片壓縮代碼,須要注意的是,img圖片渲染是異步的,因此必須在img的onlaod鉤子中再進行相應操做
img.onload = function () {
canvas.width = img.width;
canvas.height = img.height;
drawer.drawImage(img, 0, 0, canvas.width, canvas.height);
convertBase64UrlToBlob(canvas.toDataURL(file.type, quality), callback);
}
}
}
//下面是上傳部分的核心代碼
/** * 將base64格式轉化爲Blob格式 * @param {string} urlData : urlData格式的數據,經過這個轉化爲Blob對象 * @param {Function} callback : 回調函數 */
function convertBase64UrlToBlob(urlData, callback) { //將base64轉化爲文件格式
// console.log("壓縮成base64的對象:",urlData);
const arr = urlData.split(',')
// console.log("arr",arr);
const mime = arr[0].match(/:(.*?);/)[1]
const bstr = atob(arr[1]) //atob方法用於解碼base64
// console.log("將base64進行解碼:",bstr);
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
// console.log("Uint8Array:",u8arr);
callback(new Blob([u8arr], {
type: mime
}));
}
複製代碼
關於前端圖片壓縮的壓縮篇告一段落,下一篇博客將介紹前端圖片壓縮的上傳篇,重點是
也歡迎大佬在評論區留言指正博客錯誤的地方,也歡迎像我同樣的小白前端一塊兒在評論區討論。