前端图片压缩上传(压缩篇)
為什么說這是一篇比較適合小白的前端圖片壓縮文章呢?因為我也是一個剛工作半年的前端小白,最近接到了一個前端圖片壓縮上傳的任務,通過各種百度博客完成了這項任務,但是任務完成后對各種技術細節卻還不是特別理解,所以我針對我不理解的每一個技術細節都進行了記錄和學習,最后形成本篇博客,我覺得我不了解的地方可能也會有別的同學不是很了解,所以本篇博客科普向比較重,技術深度可能達不到大佬們的眼界,但也是自己的記錄和學習,加油!!!
1. 前言
如果您只是為了copy代碼實現功能,建議您不要看這篇博客了。如果您是copy了代碼實現了功能,想回來了解具體的實現流程、實現原理以及部分科普,我覺得本篇博客能給你帶來不小的收獲。
2. 任務背景
最近的項目有一個技術場景,簡單來說就是用戶需要上傳圖片至服務器。就是這么一個簡單的技術場景,但是用戶是不可控的,他們可能上傳的圖片會是幾M甚至十幾M的大小,如此大的圖片必然會導致上傳時間過長,用戶感知不好,服務器壓力大等不良影響。
作為組里唯一一個前端,老大就給了我一個任務,在用戶上傳圖片前把圖片壓縮了再上傳。那么本篇博客主要講解的就是圖片壓縮中我是如何進行壓縮的,之后的博客會講解如何將壓縮的圖片進行上傳。
3. 流程說明
本文實現的功能流程如下:
用戶通過input框選擇圖片
使用FileReader進行圖片預覽
將圖片繪制到canvas畫布上
使用canvas畫布的能力進行圖片壓縮
將壓縮后的Base64(DataURL)格式的數據轉換成Blob對象進行上傳
簡要流程圖如下:
各位同學們看到這里,不知道會不會有跟我當時一樣的疑惑?什么是 FileReader?我知道 canvas是畫布,但是具體是怎么樣用它進行圖片壓縮的呢?Base64我知道但是什么是DataURL格式的數據呢?Blob對象又是什么?
同學們,不著急,對于這些我都會進行一一講解,因為這也是作為一個小白的我初次見到這些陌生名詞時的疑惑。
4. 效果預覽
5. FileReader
FileReader這個對象是做什么的?在本次圖片壓縮中起到了什么作用?
5.1 FileReader是什么
我們先來看看MDN上對它的解釋:FileReader 對象允許Web應用程序異步讀取存儲在用戶計算機上的文件(或原始數據緩沖區)的內容,使用 File或 Blob對象指定要讀取的文件或數據。
通俗來講,就是這個對象是用來讀取 File對象或 Blob對象的。File對象就是 <inputtype="file">獲取到的對象,而 Blob(二進制)對象在本文的第5點有講解。
5.2 FileReader的鉤子與方法
作為一個js原生的用于讀取文件的對象, FileReader本身就有較為完整的鉤子函數以及一些實例方法,但是本文主要介紹圖片壓縮,所以在這里只重點講本文使用到的1個鉤子函數和1個實例方法,對其它的鉤子和方法都不做詳細介紹。
FileReader.onload:處理 load事件。即該鉤子在讀取操作完成時觸發,通過該鉤子函數可以完成例如讀取完圖片后進行預覽的操作,或讀取完圖片后對圖片內容進行二次處理等操作。
FileReader.readAsDataURL:讀取方法,并且讀取完成后, result屬性將返回 DataURL 格式(Base64 編碼)的字符串,代表圖片內容。
除了用到的這個鉤子和這個實例方法外, FileReader對象還有 onabort、 onerror、 onloadstart、 onloadend、 onprogress等鉤子;也有 abort()、 readAsArrayBuffer、 readAsBinaryString等實例方法,在次就不過多描述。
5.3 FileReader在圖片壓縮中的作用
在 onload這個鉤子對上傳的圖片實現了預覽,并且進行了圖片壓縮處理。通過 readAsDataURL()方法進行了文件的讀取,并且通過 result屬性拿到了圖片的 Base64(DataURL)格式的數據,然后通過該數據實現了圖片預覽的功能。有的同學看到這里是不是有點好奇,為什么拿到了這個 Base64(DataURL)格式的數據就能直接展示處圖片了呢?不要緊,往下看,我會在后文中解釋這個 DataURL格式的神奇。
FileReader部分代碼如下:
function canvasDataURL(file,item,callback) { //壓縮轉化為base64var 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);}}}6. canvas(圖片壓縮的核心)
canvas元素眾所周知是畫布,那么 canvas在圖片壓縮中起到了什么作用?實現圖片壓縮的核心內容主要是使用到了 canvas的什么方法?
6.1 Canvas在圖片壓縮中的作用
實現圖片壓縮最核心的地方就在 canvas這里,我們先使用 CanvasRenderingContext2D.drawImage()方法將選中的圖片文件在畫布上繪制出來,再使用 Canvas.toDataURL()將畫布上的圖片信息轉換成base64(DataURL)格式的數據。有同學會問,那么是在哪兒實現的壓縮?其實壓縮的核心就在 Canvas.toDataURL()方法的 quality參數上了,下面我們會具體介紹本文中使用到的2個 canvas畫布上的方法。
6.2 CanvasRenderingContext2D.drawImage()
語法如下:
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()方法
6.3 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的尺寸。
6.4 Canvas.toBlob()
語法如下:
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()方法呢?對于這點的解釋我會在下一篇上傳篇中進行詳解,各位同學可以持續關注我的博客。
7.DataUrl格式
不知道有心的同學發現了嗎?本文中多次提到了 DataURL格式的數據,那究竟什么才是 DataURL格式的數據呢?
在下面對 DataURL展開了解之前,我們可以先來復習一下常見的 img標簽的src是什么樣的?
那么src除了這種賦值方式之外,還有什么形式能夠展示圖片嗎?
當然有,那就是我們的 DataURL,詳見下圖:
可以看到,同樣的圖片,2種不同的src。那么 DataURL在實際中有什么用處呢?它的定義是什么?什么場景下需要用到它?帶著這個疑問看下去吧。
7.1 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的優缺點。
7.2DataURL的優缺點
這里就不做鋪墊直接說
優點:
當我們訪問外部資源很麻煩的時候比如跨域限制的時候,可以直接使用?DataURL,因為他不需要向外界發起訪問。
如果看過我Chrome調試工具NetWork模塊這篇博客的朋友可能會注意到,瀏覽器都有默認的同一時間最多同時加載的資源數量限制,比如Chrome,最多允許同時加載6個資源,其它起源就得排隊等待,但是對于?DataURL來說,它并不需要向服務器發送Http請求,它就自然不會占用一個http會話資源。
當圖片是在服務器端用程序動態生成,每個訪問用戶顯示的都不同時(emmm,這個我也不是很理解,目前暫時沒碰到過這種場景)
缺點:
有心的同學如果去嘗試就會知道,?base64的數據體積實際上會比原數據大,也就是Data URL形式的圖片會比二進制格式的圖片體積大1/3。所以如果圖片較小,使用?DataURL形式的話是比較有利的,圖片較小即使比原圖片大一些也不會大很多,相比發起一個Http會話這點開銷算什么。但是如果圖片較大的情況下,使用?DataURL的開銷就會相應增大了,具體如何取舍還得各位同學結合實際場景考量。
Data URL形式的圖片不會被瀏覽器緩存,這意味著每次訪問這樣頁面時都被下載一次。這是一個使用效率方面的問題——尤其當這個圖片被整個網站大量使用的時候。(這個缺點可以規避,具體可以自行百度或看其它的博客,這里不細說)
那么總結一下, DataURL帶來的便利原因就是一個:它不需要發起http請求;而它的缺點歸納起來就是兩個:體積比原有還要大、不會被緩存。
7.3 本文為何使用DataURL
看完上面的科普,同學們是不是都了解了 DataURL的優缺點,那么不知道各位是否會好奇,本次前端壓縮為何使用的是 DataURL呢?
答案其實在第一小節中有,因為我們想做圖片預覽,而圖片預覽是通過 FileReader對象實現的, FileReader會去讀文件,并且返回文件的內容,但是返回的內容要么就是二進制字符串,要么就是二進制字節數組,這些都不能直接用于圖片展示,只有調用 FileReader.readAsDataURL()返回的 DataURL格式的數據可以直接使用。
8. 代碼
因為公司使用的前端框架比較非主流,是layUI框架的,所以我的代碼也是基于layUI的前端壓縮(所以你們直接copy我的代碼大概率是跑不起來的),不過核心思想核心方法核心原理在博客中都有體現,只要理解了思想,其實不難將它復刻到你們自己的項目中去。
代碼如下:
//這是layUI的文件上傳組件,不了解的朋友也沒必要去看文檔,理解思想就行了 var upload1 = upload.render({elem: '.uploadImg',url: releaseUrl,accept: 'images',auto: false,//選擇文件后的鉤子函數choose: function (obj) {var that = this;//預讀本地文件示例,不支持ie8obj.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) { //壓縮轉化為base64var reader = new FileReader(); //讀取文件的對象reader.readAsDataURL(file); //對文件讀取,讀取完成后會將內容以base64的形式賦值給result屬性reader.onload = function (e) { //讀取完成的鉤子const img = new Image();const quality = 0.2; // 圖像質量//先創建canvas畫布,再獲取canvas畫布上的2d繪圖環境,通過這個2d繪圖環境才可使用繪制APIconst canvas = document.createElement('canvas'); //創建canvas畫布const drawer = canvas.getContext('2d'); //返回一個在畫布上繪制2d圖的環境對象,該對象上包含有canvas繪制2d圖形的APIimg.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.lengthconst u8arr = new Uint8Array(n)while (n--) {u8arr[n] = bstr.charCodeAt(n)}// console.log("Uint8Array:",u8arr);callback(new Blob([u8arr], {type: mime}));}9. 總結
關于前端圖片壓縮的壓縮篇告一段落,下一篇博客將介紹前端圖片壓縮的上傳篇,重點是
Blob對象
Base64轉Blob的方法
Uint8Array
為什么要先壓縮為Base64(DataURL)?再將Base64(DataURL)轉化為Blob對象?)。
也歡迎大佬在評論區留言指正博客錯誤的地方,也歡迎像我一樣的小白前端一起在評論區討論。
10.參考文章
玩轉圖片流
MDN_FileReader相關文檔
canvasApi中文網
DataURL與base64
作者:劉偉C
https://juejin.im/post/5e42523ae51d4527031358f4
總結
以上是生活随笔為你收集整理的前端图片压缩上传(压缩篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Juniper认证,网络工程师的第二语言
- 下一篇: 计算机测绘程序设计实验报告,测绘程序设计