使用 JavaScript 壓縮圖片並上傳
☘️WEB前端上傳檔案到遠端時,我們都會遇到同樣一個問題,請求長度超出限制,是的,檔案大小超出限制該如何解決?
我這裡有兩種方案:
- 第一種,前端壓縮後上傳圖片
- 第二種,分塊上傳,後臺進行壓縮儲存
- 其實有第三種,不允許上傳此檔案 ?
如果只考慮第一種方案,前端壓縮圖片後上傳,對於壓縮圖片的大小和質量無法把控,並且非圖片型別的檔案上傳又要考慮非圖片型別檔案的壓縮方法。
第二種方式,實現分塊上傳對編碼要求更高,後端和前端都需要實現可靠的元件。
本篇文章,我們只瞭解如何使用前端壓縮圖片
我畫了一張圖,大致描述前端壓縮圖片整個流程呼叫
使用 JavaScript
壓縮圖片,首先要學習三個Web API
: FileReader
,Canvas
,Blob
FileReader
FileReader 物件允許Web應用程式非同步讀取儲存在使用者計算機上的檔案(或原始資料緩衝區)的內容,使用 File 或 Blob 物件指定要讀取的檔案或資料。
成員方法:
FileReader.abort()
中止讀取操作。在返回時,readyState
屬性為DONE
。FileReader.readAsArrayBuffer()
開始讀取指定的Blob
中的內容, 一旦完成,result
屬性中儲存的將是被讀取檔案的ArrayBuffer
資料物件.FileReader.readAsBinaryString()
開始讀取指定的Blob中的內容。一旦完成,result屬性中將包含所讀取檔案的原始二進位制資料。FileReader.readAsDataURL()
開始讀取指定的Blob
中的內容。一旦完成,result
屬性中將包含一個data: URL
格式的字串以表示所讀取檔案的內容。FileReader.readAsText()
開始讀取指定的Blob中的內容。一旦完成,result
屬性中將包含一個字串以表示所讀取的檔案內容。
事件處理:
FileReader.onabort
處理abort
事件。該事件在讀取操作被中斷時觸發。FileReader.onerror
處理error
事件。該事件在讀取操作發生錯誤時觸發。FileReader.onload
處理load
事件。該事件在讀取操作完成時觸發。FileReader.onloadstart
處理loadstart
事件。該事件在讀取操作開始時觸發。FileReader.onloadend
處理loadend
事件。該事件在讀取操作結束時(要麼成功,要麼失敗)觸發。FileReader.onprogress
處理progress
事件。該事件在讀取Blob
時觸發
圖片壓縮過程中,我們需要使用到FileReader.readAsDataURL()
將檔案轉換為DataURL
,並使用FileReader.onload
事件接收轉換結果。
程式碼實現:
var ready = new FileReader();
ready.readAsDataURL(file);
ready.onload = function(){
var re = this.result;
// re 為 Base64 DataURL
}
Canvas
HTML5 <canvas>
標籤用於繪製影象(通過指令碼,通常是 JavaScript)。不過,<canvas>
元素本身並沒有繪製能力(它僅僅是圖形的容器) - 您必須使用指令碼來完成實際的繪圖任務。
- getContext() 方法可返回一個物件,該物件提供了用於在畫布上繪圖的方法和屬性
- getContext("2d") 物件屬性和方法,可用於在畫布上繪製文字、線條、矩形、圓形等等
drawImage drawImage() 方法在畫布上繪製影象、畫布或視訊
三種過載方式:
-
context.drawImage(img,x,y); // 在畫布上定點陣圖像
-
context.drawImage(img,x,y,width,height); // 在畫布上定點陣圖像,並規定影象的寬度和高度
-
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height); // 剪下影象,並在畫布上定位被剪下的部分
圖片裁剪過程中,我們將會使用第二種方式,壓縮修改圖片的大小(實現等比例縮放)
程式碼實現
var img = new Image();
img.src = path;
img.onload = function(){
var that = this;
// 等比例縮放
var w = that.width,
h = that.height,
scale = w / h;
w = obj.width || w;
h = obj.height || (w / scale);
var quality = 0.7;
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var anw = document.createAttribute("width");
anw.nodeValue = w;
var anh = document.createAttribute("height");
anh.nodeValue = h;
canvas.setAttributeNode(anw);
canvas.setAttributeNode(anh);
ctx.drawImage(that, 0, 0, w, h);
if(obj.quality && obj.quality <= 1 && obj.quality > 0){
quality = obj.quality;
}
var base64 = canvas.toDataURL('image/jpeg', quality);
}
Blob
Blob
物件表示一個不可變、原始資料的類檔案物件。Blob
表示的不一定是JavaScript
原生格式的資料。File
介面基於Blob
,繼承了blob
的功能並將其擴充套件使其支援使用者系統上的檔案。
要從其他非blob
物件和資料構造一個Blob
,請使用Blob()
建構函式。要建立包含另一個blob
資料的子集blob
,請使用slice()
方法。
此處 slice()
也是用於分塊上傳的呼叫方法
本文前端壓縮圖片流程,我們只需要用到Blob()
建構函式
下面程式碼實現:DataURL 建立 Blob 物件,提供上傳表單form-data
使用:
function dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {type:mime});
}
圖片拍攝角度問題
首先來了解一個概念 EXIF
是什麼?
簡單來說,Exif
資訊就是由數碼相機在拍攝過程中採集一系列的資訊,然後把資訊放置在我們熟知的JPEG/TIFF
檔案的頭部,也就是說Exif
資訊是鑲嵌在 JPEG/TIFF
影象檔案格式內的一組拍攝引數。主要包含了以下幾類資訊:
- 拍攝日期
- 攝器材,機身、鏡頭、閃光燈等
- 拍攝引數,快門速度、光圈F值、ISO速度、焦距、測光模式等
- 影象處理引數,銳化、對比度、飽和度、白平衡等
- 影象描述及版權資訊
- GPS定位資料
...
其中orientation
記錄著拍攝後圖片旋轉的角度資訊:https://www.impulseadventure.com/photo/exi...
orientation | 旋轉角度 |
---|---|
1 | 0° |
3 | 180° |
6 | 順時針90° |
8 | 逆時針90° |
所以,上傳圖片時,需要將圖片進行反向旋轉,才能得到一致的展示效果。
開始旋轉圖片之前,我們需要獲取圖片旋轉資訊,需要引入擴充套件:https://github.com/exif-js/exif-js
然後,就可以編寫程式碼實現:(所有的旋轉都是以原點為中心的)
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const anw = document.createAttribute('width');
const anh = document.createAttribute('height');
anw.nodeValue = w;
anh.nodeValue = h;
switch (orientation) {
case 6: // 90度
anw.nodeValue = h;
anh.nodeValue = w;
canvas.setAttributeNode(anh);
canvas.setAttributeNode(anw);
ctx.rotate(Math.PI / 2);
ctx.drawImage(this, 0, -h, w, h);
break;
case 3: // 180度
canvas.setAttributeNode(anh);
canvas.setAttributeNode(anw);
ctx.rotate(Math.PI);
ctx.drawImage(this, -w, -h, w, h);
break;
case 8: // -90度
anw.nodeValue = h;
anh.nodeValue = w;
canvas.setAttributeNode(anh);
canvas.setAttributeNode(anw);
ctx.rotate(3 * Math.PI / 2);
ctx.drawImage(this, -w, 0, w, h);
break;
default: // 0度
canvas.setAttributeNode(anh);
canvas.setAttributeNode(anw);
ctx.drawImage(this, 0, 0, w, h);
}