需求
客戶端上傳圖片到服務器。大部分情況下,客戶端的圖片質量,遠大於業務實際需要。上傳,存儲和下載展示,多出的都是浪費,節能環保,從源頭做起。
實現原理
實現圖片的壓縮,實際上就是改變圖片的尺寸(寬和高),或者改變圖片的體積。使用CanvasRenderingContext2D.drawImage()和HTMLCanvasElement.toDataURL()這兩個API,就可以實現這兩種操作。CanvasRenderingContext2D.drawImage() 可以將Image寫入到canvas中,寫入過程中可以自定義畫布上繪制圖像的寬度和高度,從而實現對圖片尺寸的改變。HTMLCanvasElement.toDataURL()可以將canvas的內容,導出成圖片,導出時可以指定圖片的質量,從而改變圖片的體積。
但是我們從input得到的文件數據是File類型的,需要轉變成CanvasRenderingContext2D.drawImage()能夠寫入的HTMLImageElement,HTMLCanvasElement.toDataURL()導出的數據格式是DataURL,而我們上傳時需要Blob類型的數據,因此要實現得到文件,壓縮,然后上傳,我們需要對數據做一系列的處理。如同流水線加工,塞進去一只活蹦亂跳的大母雞,最后得到一盒全家桶。
實現過程
實現過程分為三個部分:
- 得到目標文件
- 壓縮
- 上傳
第一步:得到目標文件
var input = document.getElementById('file'); input.addEventListener('change',function(e){ var file = this.files[0]; fileName = file.name; });
第二步:壓縮
壓縮過程中,圖片對應的數據類型在不斷變化,按照類型變化,分為7個步驟
- File轉化成Data URIs
- Data URIs轉化成HTMLImageElement
- HTMLImageElement寫入canvas
- canvas轉化成DataURL
- DataURL轉化成二進制String
- 二進制String轉化成ArrayBuffer
- ArrayBuffer轉化成Blob
1.File轉化成Data URIs
function fileToDataURL(file,callback) { var reader = new FileReader(); reader.onload = function () { var result = this.result; callback(result); } reader.readAsDataURL(file); }
2.Data URIs轉化成HTMLImageElement
function dataURLToImage(dataURL,callback) { var img = new Image(); img.onload = function() { callback(img); } img.src = dataURL; }
3.HTMLImageElement寫入canvas
創建canvas之后,可以設置canvas的寬和高。圖片寫入canvas的過程中,可以指定寫入后的寬和高。這個過程,就是對圖片尺寸進行更改。
4.canvas轉化成DataURL
這個過程中需要注意,canvas.toDataURL(type, encoderOptions); 當type為''image/jpeg''或者'image/webp'時,encoderOptions指定的圖片質量系數才是有效的。
function compress(img) { var canvas = document.createElement('canvas'), width = img.width, height = img.height; canvas.width = width; canvas.height = height; var context = canvas.getContext('2d'); context.drawImage(img, 0, 0, width, height); var dataUrl = canvas.toDataURL('image/jpeg',0.7); return dataUrl; }
5.DataURL轉化成二進制String
6.二進制String轉化成ArrayBuffer
7.ArrayBuffer轉化成Blob
function dataUrlToBlob(dataUrl) { var text = window.atob(dataUrl.split(",")[1]); var buffer = new ArrayBuffer(text.length); var ubuffer = new Uint8Array(buffer); for (var i = 0; i < text.length; i++) { ubuffer[i] = text.charCodeAt(i); } var blob; var type = 'image/jpeg'; blob = new window.Blob([buffer], {type: type}); return blob; }
第三步:上傳
得到圖片對應的Blob數據,使用FormData發送到服務端。
function upload(blob,name) { var formData = new FormData(); name = name.substring(0,name.lastIndexOf('.')) + '.jpeg'; formData.append('file', blob ,name); formData.append('name',name); formData.append('size',blob.size); formData.append('type','image/jpeg'); var xhr = new XMLHttpRequest(); xhr.open('post','/fedemos/server/fileupload.php'); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { console.log() } } xhr.send(formData); }
代碼示例
<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <title></title> <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> </head> <body> <input id="file" type="file" name="file" > <script type="text/javascript"> var input = document.getElementById('file'); input.addEventListener('change',function(e){ var file = this.files[0]; fileName = file.name; imageCompress(file,function(blob){ upload(blob,fileName); }) }); function imageCompress(file,callback) { fileToDataURL(file,function(dataURL){ dataURLToImage(dataURL,function(img){ transform(img) }) }) function fileToDataURL(file,callback) { var reader = new FileReader(); reader.onload = function () { var result = this.result; callback(result); } reader.readAsDataURL(file); } function dataURLToImage(dataURL,callback) { var img = new Image(); img.onload = function() { callback(img); } img.src = dataURL; } function transform(img) { var dataUrl = compress(img); var blob = dataUrlToBlob(dataUrl); callback(blob); } function compress(img) { var canvas = document.createElement('canvas'), width = img.width, height = img.height; // if(width>1280 && width>height) { // height = height*1280/width; // width = 1280; // } else if(height>width && height>1280) { // width = width*1280/height; // height = 1280; // } canvas.width = width; canvas.height = height; var context = canvas.getContext('2d'); context.drawImage(img, 0, 0, width, height); var dataUrl = canvas.toDataURL('image/jpeg',0.7); return dataUrl; } function dataUrlToBlob(dataUrl) { var text = window.atob(dataUrl.split(",")[1]); var buffer = new ArrayBuffer(text.length); var ubuffer = new Uint8Array(buffer); for (var i = 0; i < text.length; i++) { ubuffer[i] = text.charCodeAt(i); } var blob; var type = 'image/jpeg'; blob = new window.Blob([buffer], {type: type}); return blob; } } function upload(blob,name) { var formData = new FormData(); name = name.substring(0,name.lastIndexOf('.')) + '.jpeg'; formData.append('file', blob ,name); formData.append('name',name); formData.append('size',blob.size); formData.append('type','image/jpeg'); var xhr = new XMLHttpRequest(); xhr.open('post','/fedemos/server/fileupload.php'); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { console.log() } } xhr.send(formData); } </script> </body> </html>