對於大尺寸圖片的上傳,在前端進行壓縮除了省流量外,最大的意義是極大的提升了用戶體驗。javascript
這種體驗包括兩方面:php
一、因爲上傳圖片尺寸比較小,所以上傳速度會比較快,交互會更加流暢,同時大大下降了網絡異常致使上傳失敗風險。html
二、最重要的體驗改進點:省略了圖片的再加工成本。不少網站的圖片上傳功能都會對圖片的大小進行限制,尤爲是頭像上傳,限制5M或者2M之內是很是常見的。而後如今的數碼設備拍攝功能都很是出衆,一張原始圖片超過2M幾乎是標配,此時若是用戶想把手機或相機中的某個得意圖片上傳做爲本身的頭像,就會遇到由於圖片大小限制而不能上傳的窘境,不得不對圖片進行再處理,而這種體驗其實很是很差的。若是能夠在前端進行壓縮,則理論上對圖片尺寸的限制是沒有必要的。前端
要想使用JS實現圖片的壓縮效果,原理其實很簡單,核心API就是使用canvas
的drawImage()
方法。java
canvas
的drawImage()
方法API以下:jquery
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個參數:web
canvas
畫布上規劃處一片區域用來放置圖片,dx, dy
爲canvas元素的左上角座標,dWidth, dHeight
指canvas元素上用在顯示圖片的區域大小。若是沒有指定sx,sy,sWidth,sHeight
這4個參數,則圖片會被拉伸或縮放在這片區域內。
canvas
畫布上顯示的大小和位置。sx,sy
表示圖片上sx,sy
這個座標做爲左上角,而後往右下角的swidth,sheight
尺寸範圍圖片做爲最終在canvas上顯示的圖片內容。
drawImage()
方法有一個很是怪異的地方,你們必定要注意,那就是5參數和9參數裏面參數位置是不同的,這個和通常的API有所不一樣。通常API可選參數是放在後面。可是,這裏的drawImage()
9個參數時候,可選參數sx,sy,swidth,sheight
是在前面的。若是不注意這一點,有些表現會讓你沒法理解。ajax
下圖爲MDN上原理示意:canvas
對於本文的圖片壓縮,須要用的是是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);
把一張大的圖片,直接畫在一張小小的畫布上。此時大圖片就自然變成了小圖片,壓縮就這麼實現了,是否是簡單的有點超乎想象。
固然,若要落地於實際開發,咱們還須要作些其餘的工做,就是要解決圖片來源和圖片去向的問題。
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
圖片就有了。
二、如何把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(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>