HTML5 圖片上傳解決方案
前端做圖片上傳時,經常會遇到圖片壓縮、圖片預覽等需求。而這個過程中,會遇到一個個的坑。下面就來看一看 HTML5 實現圖片上傳的整個過程。
基本結構
圖片上傳是使用 input 標籤來選擇圖片的:
<input type="file" accept="image/*">
這裡可能遇到一個坑:
可能會遇到響應遲鈍,檔案選擇框過好幾秒才彈出。 ofollow,noindex">具體的原因可以檢視這裡 。
解決的方法是將 * 萬用字元改成指定的 MIME 型別。例如
<input type="file" accept="image/gif,image/jpeg,image/jpg,image/png">
獲取圖片檔案
通過監聽 input 的 change
事件,獲取 FileList
類陣列物件(event.target.files)。 FileList
物件的成員就是 File
物件,包含的屬性如圖:
FileReader
要對圖片進行壓縮,首先要獲取圖片內容。
這裡需要使用 FileReader
的 readAsDataURL(Blob|File)
來讀取圖片內容。 readAsDataURL
方法返回 data URL,將檔案進行 base64 編碼。示例如下:
let fr = new FileReader(); // 讀取成功回撥 fr.onload = e => { // e.target.result 就是圖片的 base64 地址,可以直接用於圖片的 src img.src = e.target.result; } // 失敗回撥 fr.onerror = e => { // error handle } // 讀取圖片 fr.readAsDataURL(file);
方法參考: API/FileReader" target="_blank" rel="nofollow,noindex">FileReader
圖片壓縮
前端進行圖片壓縮,不但節省流量,而且加速上傳速度,提高使用者體驗。
上一步讀取了圖片,就可以對圖片進行操作了。前端實現圖片壓縮,原理很簡單:
- 縮小圖片,大圖片轉成小圖片
- 降低圖片質量
第一點是通過 canvas
的 drawImage()
方法來實現,第二點在後面會提到。
void ctx.drawImage(image, dx, dy, dWidth, dHeight)
是在 canvas 上繪製圖像,我們只需要把原圖,在 canvas 上繪製成更小的圖片,就實現了壓縮,就是這麼簡單。
所以關鍵就是怎麼來設定 dWidth
和 dHeight
。
一般的做法是限制圖片的最大長度和寬度,超過則等比例縮放。例如:
// width height 圖片長寬 // maxWidth maxHeight 限制的圖片最大長寬 let scale = width / height; if (scale >= maxWidth / maxHeight) { if (width > maxWidth) { height = maxWidth / scale; width = maxWidth; } } else if (height > maxHeight) { width = maxHeight * scale; height = maxHeight; } // 這裡的 image 就是上一步得到的圖片內容 ctx.drawImage(image, 0, 0, width, height);
方法參考: CanvasRenderingContext2D/drawImage" target="_blank" rel="nofollow,noindex">CanvasRenderingContext2D.drawImage()
圖片輸出
上一步在 canvas 繪畫了圖片,接下來就需要把 canvas 畫布轉化成 img 影象。
canvas
提供了兩個轉圖片的方法:
- HTMLCanvasElement.toDataURL() :圖片轉換成base64格式
- HTMLCanvasElement.toBlob() :圖片轉換成Blob檔案
toDataURL()
canvas.toDataURL(type, encoderOptions);
屬於同步方法,返回 base64 格式的圖片。
第一個引數 type
是圖片格式;
第二個引數 encoderOptions
就是用於之前提到的,控制圖片質量,達到壓縮圖片的效果。
在指定圖片格式為 image/jpeg 或
image/webp的情況下,可以從 0 到 1 的區間內選擇圖片的質量
。如果超出取值範圍,將會使用預設值 0.92
。
toBlob()
void canvas.toBlob(callback, type, encoderOptions);
屬於非同步方法,所以有個 callback
引數。
type
引數指定圖片格式;
encoderOptions
引數指定圖片質量,用於壓縮圖片
值在0與1之間,當請求圖片格式為 image/jpeg
或者 image/webp
時用來指定圖片展示質量。
關於 Blob:
Blob
物件表示一個不可變、原始資料的類檔案物件。Blob 表示的不一定是JavaScript原生格式的資料。 File
介面基於 Blob
,繼承了 blob 的功能並將其擴充套件使其支援使用者系統上的檔案。
JavaScript 中 Blob 物件 : 這篇文章介紹了 Blob,裡面也提到了大檔案分割上傳的實現。
一般來說,對比 Blob 檔案和 base64 ,有下面幾點優點:
- 二進位制檔案,對後端更友好
- base64 字串一般都非常長,會有效能等問題
所以選擇轉化成 Blob 檔案進行上傳更好。把 base64 或者 Blob 檔案加入 FormData
裡就可以實現上傳了。
圖片預覽
一般圖片上傳還會要求實現圖片上傳進度、圖片預覽功能。
關於圖片預覽的實現,可以通過下面的方法,來獲取圖片連結,做為本地預覽:
-
base64 可以作為圖片連結,
FileReader.readAsDataURL(Blob|File)
方法可以得到 base64 -
URL.createObjectURL(Blob|File)
返回的 URL 可以作為圖片的連結
詳情參考:
手機照片旋轉問題
把在 iPhone 上拍出來的照片,通過上面的方式進行上傳,你會發現圖片方向和你預料的不同。
orientation
使用 iPhone 拍照片,會根據你拍照時手機的方向,照片會有不同的方向。這個方向可以通過圖片的 orientation
引數來確定。
旋轉角度 | 引數值 | 手機方向 |
---|---|---|
0° | 1 | home 鍵在右方的橫屏拍攝方式 |
逆時針90° | 6 | home鍵在下方(正常拿手機的方向) |
順時針90° | 8 | home鍵在上方 |
180° | 3 | home鍵在左側 |
可以通過 exif-js 來獲取圖片的 orientation
:
import EXIF from 'exif-js'; // file 是上面提到的 File 檔案 EXIF.getData(file, function() { var orientation = EXIF.getTag(this, 'Orientation'); });
詳情參考: iOS/" target="_blank" rel="nofollow,noindex">如何處理iOS中照片的方向
校正方向
要校正圖片的方向,只需要根據 orientation
引數,把圖片的方向旋轉會正常即可。旋轉需要用到 canvas 的 rotate()
方法。
void ctx.rotate(angle);
引數 angle 是順時針旋轉的弧度,旋轉中心點是 canvas 的起始點。
角度值換算弧度的公式: angle = degree * Math.PI / 180
以 orientation
等於 6 時為例,也就是圖片逆時針旋轉了 90°,要把圖片校正方向,就要畫布順時針旋轉 90° : rotate((90 * Math.PI) / 180)
畫布旋轉之後, drawImage()
根據畫布的位置進行調整,如上圖所示:
旋轉之前: drawImage(image, 0, 0, x, y)
旋轉之後,原有的畫布不變,座標跟著旋轉,圖片轉到可視範圍之外,所以:
- 需要把圖片移到可視範圍裡
- 調整畫布大小
// other code... case 6: // 旋轉角度 degree = 90; // 調整畫布大小 canvas.width = imgHeight; canvas.height = imgWidth; // 修改繪畫位置 imgHeight = - imgHeight; break; // other code ... // canvas 旋轉 ctx.rotate((degree * Math.PI)/180); // 繪製圖片 ctx.drawImage(image, 0, 0, imgWidth, imgHeight);
其他的 orientation
也是類似的原理。 檢視示例程式碼
總結
圖片壓縮上傳的過程,總結起來就是:圖片 → 壓縮 → 圖片。
這個過程中,核心點是:
-
FileReader API 使用
-
Canvas 實現圖片壓縮、繪製和方向校正
這裡主要總結了使用 HTML5 API 實現圖片上傳的過程,在實際的使用還要根據具體的使用場景,考慮相容問題,選擇合適的解決方案。
最後,檢視完整的示例程式碼: h5ImgCompress