前端圖片上傳那些事兒
本文講的圖片上傳,主要是針對上傳頭像的,大家都知道,上傳頭像一般都會分成以下 4 個步驟:
選擇圖片 -> 預覽圖片 -> 裁剪圖片 -> 上傳圖片
接下來,就詳細的介紹每個步驟具體實作,
選擇圖片
選擇圖片有什么好講的呢?不就一個 input[type=file] ,然后點擊就可以了嗎?確實是這樣的,但是,我們想要做得更加的友好一些,比如需要過濾掉非圖片檔案, 或只允許從攝像頭拍斬訓取圖片等,還是需要進行一些簡單配置的,
下面就先來看看最簡單的選擇圖片:
<input type="file" />
這時候,點擊這個 input , 在 iOS 手機的顯示如下:

其中的 “瀏覽” 選項,可以查看到非圖片型別的檔案,這并不是我們想要的結果,畢竟我們只想要圖片型別,可以通過 accept 屬性來實作,如下:
<input type="file" accept="image/*">
這樣就可以過濾掉非圖片型別了,但是圖片的型別可能也太多了, 有些可能服務器不支持,所以,如果想保守一些,只允許 jpg 和 png 型別,可以寫成這樣:
<input type="file" accept="image/jpg, image/jpeg, image/png">
或:
<input type="file" accept=".jpg, .jpeg, .png">
OK, 過濾非圖片的需求搞定了,但是有時候 ,產品還要求只能從攝像頭采集圖片,比如需要上傳證件照,防止從網上隨便找別人的證件上傳,那 capture 屬性就可以派上用場了:
<input type="file" accept="image/*" capture>
這時候,就不能從檔案系統中選擇照片了,只能從攝像頭采集,到了這一步,可能覺得很完美了,但是還有個問題,可能有些變態產品要求默認打開前置攝像頭采集圖片,比如就是想要你的自拍照片, capture 默認呼叫的是后置攝像頭,默認啟用前置攝像頭可以設定 capture="user" ,如下:
<input type="file" accept="image/*" capture="user">
好啦,關于選擇圖片的就講么這么多了,有個注意的地方是,可能有些配置在兼容性上會有一些問題,所以需要在不同的機型上測驗一下看看效果,
下面再來談談預覽圖片的實作,
預覽圖片
在遠古時代,前端并沒有預覽圖片的方法,當時的做法時,用戶選擇圖片之后,立刻把圖片上傳到服務器,然后服務器回傳遠程圖片的 url 給前端顯示,這種方法略顯麻煩,而且會浪費用戶的流量,因為用戶可能還沒有確定要上傳,你卻已經上傳了,幸好,遠古時代已經離我們遠去了,現代瀏覽器已經實作了前端預覽圖片的功能,常用的方法有兩個,分別是 URL.createObjectURL() 和 FileReader ,雖然他們目前均處在 w3c 規范中的 Working Draft 階段, 但是大多數的現代瀏覽器都已經良好的支持了, 下面就介紹一下如何使用這兩個方法,
1. 使用 URL.createObjectURL 預覽
URL.createObjectURL() 靜態方法會創建一個 DOMString,其中包含一個表示引數中給出的物件的 URL,這個 URL 的生命周期和創建它的視窗中的 document 系結,這個新的URL 物件表示指定的 File 物件或 Blob 物件,用法用下:
objectURL = URL.createObjectURL(object);
其中, object 引數指 用于創建 URL 的 File 物件、Blob 物件或者 MediaSource 物件,
對于我們的 input[type=file] 而言, input.files[0] 可以獲取到當前選中檔案的 File 物件,示例代碼如下:
<input id="inputFile" type="file" accept="image/*"> <img src="" id="previewImage" alt="圖片預覽"> <script> const $ = document.getElementById.bind(document); const $inputFile = $('inputFile'); const $previewImage = $('previewImage'); $inputFile.addEventListener('change', function() { const file = this.files[0]; $previewImage.src = file ? URL.createObjectURL(file) : ''; }, this); </script>
具體用法可以參考 MDN上的 URL.createObjectURL(),
2. 使用 FileReader 預覽
FileReader 物件允許Web應用程式異步讀取存盤在用戶計算機上的檔案(或原始資料緩沖區)的內容,使用 File 或 Blob 物件指定要讀取的檔案或資料,同理的,我們也可以通過 input.files[0] 獲取到當前選中的圖片的 File物件,
特別注意,FileReader 和 是異步讀取檔案或資料的!
下面是使用 FileReader 預覽圖片的示例:
<input id="inputFile" type="file" accept="image/*"> <img src="" id="previewImage" alt="圖片預覽"> <script> const $ = document.getElementById.bind(document); const $inputFile = $('inputFile'); const $previewImage = $('previewImage'); $inputFile.addEventListener('change', function() { const file = this.files[0]; const reader = new FileReader(); reader.addEventListener('load', function() { $previewImage.src = reader.result; }, false); if(file) { reader.readAsDataURL(file); } }, false) </script>
會發現, FileReader 會相對復雜一些.
更多關于 FileReader 的用法 ,可以參考 MDN 檔案 FileReader
兩種方法的對比
我個人更加傾向于使用 URL.createObjectURL() ,主要原先它的 API 簡潔,同步讀取,并且他回傳的是一個 URL ,比 FileReaer 回傳的base64 更加精簡,兼容性上,兩者都差不多,都是在 WD 的階段,性能上的對比, 在 chrome 上, 選擇了一張 2M 的圖片, URL.createObjectURL() 用時是 0 , 而 FileReader 用時 20ms 左右, 0 感覺不太合理,雖然這個方法立刻就會回傳一個 URL ,但是我猜測實際上這個 URL 指定的內容還沒有生成好,應該是異步生成的,然后才渲染出來的,所以并沒有很好的辦法來對比他們的性能,
如果想要學習更多關于圖片預覽,可以閱讀以下兩篇文章:
- 使用FileReader實作前端圖片預覽
- js圖片/視頻預覽 - URL.createObjectURL()
裁剪圖片
關于圖片的裁剪,很自然的會想到使用 canvas ,確實是要通過 canvas, 但是如果全部我們自己來實作,可能需要做比較多的作業,所以為了省力,我們可以站在巨人的肩膀上,比較優秀的圖片裁剪庫是 cropperjs , 該庫可以對圖片進行縮放、移動和旋轉,
cropperjs 的詳細配置這里就不展開了 ,需要的可以自己去看檔案就好,下面我們就以這個庫為基礎,實作一個裁剪人臉的例子:
<input id="inputFile" type="file" accept="image/*"> <img class="preview-image" id="previewImage" src="" alt=""> <!-- cropper裁剪框 --> <div class="cropper" id="cropper"> <div class="inner"> <div class="face-container"> <img class="cropper-image" id="cropperImage"> </div> <div class="tips">請將面部區域置于人臉框架內</div> <div class="toolbar"> <div class="btn" id="confirm">確認</div> </div> </div> </div> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.css"> <script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.js"></script> <style> .preview-image, .cropper-image { max-width: 100%; } .cropper { display: none; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: #ccc; font-size: 0.27rem; text-align: center; } .inner { height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; } .face-container { position: relative; width: 320px; height: 320px; margin: 50px auto; } .cropper-modal { background: url('https://ok.166.net/gameyw-misc/opd/squash/20191028/152551-m37snfsyu1.png') center no-repeat; background-size: 100% 100%; opacity: 1; } .cropper-bg { background: none; } .cropper-view-box { opacity: 0; } .tips { font-size: 16px; } .toolbar { display: flex; justify-content: center; margin: 50px 0; } .btn { width: 150px; line-height: 40px; font-size: 20px; text-align: center; color: #fff; background: #007fff; } </style> <script> const $ = document.getElementById.bind(document); const $cropper = $('cropper'); const $inputFile = $('inputFile'); const $previewImage = $('previewImage'); const $cropperImage = $('cropperImage'); const $confirmBtn = $('confirm') let cropperInstance = null; // 選擇圖片后,顯示圖片裁剪框 $inputFile.addEventListener('change', function() { const file = this.files[0]; if(!file) return; $cropperImage.src = URL.createObjectURL(file); showCropper(); }, false); // 點擊確認按鈕,將裁剪好的圖片放到 img 標簽顯示, $confirmBtn.addEventListener('click', function() { const url = cropperInstance.getCroppedCanvas().toDataURL("image/jpeg", 1.0); $cropper.style.display = 'none'; $previewImage.src = url; }, false); function showCropper() { $cropper.style.display = 'block'; cropperInstance && cropperInstance.destroy(); cropperInstance = new Cropper($cropperImage, { viewMode: 1, aspectRatio: 1, autoCropArea: 1, dragMode: 'move', guides: false, highlight: false, cropBoxMovable: false, cropBoxResizable: false }); } </script>
效果圖如下:
上傳
前面的操作已經完成了圖片上傳前的準備,包括選擇圖片、預覽圖片、編輯圖片等,那接下來就可以上傳圖片了,上面的例子中,使用了 cropperInstance.getCroppedCanvas() 方法來獲取到對應的 canvas 物件 ,有了 canvas 物件就好辦了,因為 canvas.toBlob() 方法可以取得相應的 Blob 物件,然后,我們就可以把這個 Blob 物件添加到 FromData 進行無重繪的提交了,大概的代碼如下:
function uploadFile() {
cropperInstance.getCroppedCanvas().toBlob(function(blob) {
const formData = https://www.cnblogs.com/Army-Knife/p/new FormData();
formData.append('avatar', blob);
fetch('xxxx', {
method: 'POST',
body: formData
});
});
}
這段代碼并不能真正執行,因為我們還沒有對應的后端服務器,如果想要嘗試上傳圖片的朋友,可以參考一下這篇文章 寫給新手前端的各種檔案上傳攻略,從小圖片到大檔案斷點續傳,由于篇幅原因,這里就不展開啦,
后記
關于圖片上傳的介紹,差不多不到些結束了,但是之前在 iPhone 和 小米 手機上,遇到一個奇怪的問題: 就是我使用前置攝像頭自拍出來的照片,選擇之后 ,會自逆時針旋轉 90 度,比如像下圖:

拍照的時候明明就是正著拍的,為什么預覽就會變成橫著了呢?當時第一次遇到這個問題的時候,也覺得好奇怪,后來查了一下,得知這是因為拍照時,相機都會記錄拍照的角度資訊,可能 iPhone 前置攝像頭記錄的角度資訊和其他的有點不一樣,而 iPhone 自己的相冊在瀏覽照片時,自動糾正了角度 ,而瀏覽器卻沒有糾正,所以才會出現這個旋轉,
為了解決這個問題,需要使用 EXIF 這個庫來處理,
我剛剛試了一下,發現我的 iPhone 現在竟然不會有這個問題了,大概是半年前,當時在做一個需求時,自拍的圖片會發生這種旋轉,有可能是 iOS 系統升級后, 已經修復了這個問題,而現在身邊又沒有小米手機, 所以也不好復現,還好,當時我保存了一張會自動旋轉的圖片,大家可以到這里下載:
https://ok.166.net/gameyw-misc/opd/squash/20191028/170829-f5t38i0d9k.png
這圖片下載后,用電腦的圖片查看器打開是正常的,但是,在瀏覽器中,選擇這個圖片后,使用 URL.createObjectURL() 或 FileReader 來預覽就會發生旋轉,甚至直接 img 標簽引入也會逆時針旋轉了 90 度,比如:
<img src="https://ok.166.net/gameyw-misc/opd/squash/20191028/170829-f5t38i0d9k.png">
效果如下:
下面就以這張圖片為例,介紹一下如何使用 EXIF 來檢測圖片角度,關于 EXIF 的詳細用法大家可以到 github 的主頁上查看 https://github.com/exif-js/exif-js
<img id="exifImage" src="https://ok.166.net/gameyw-misc/opd/squash/20191028/170829-f5t38i0d9k.png" alt=""> <script src="https://cdnjs.cloudflare.com/ajax/libs/exif-js/2.3.0/exif.js"></script> <script> const $exifImage = document.getElementById('exifImage'); $exifImage.onload = function() { EXIF.getData($exifImage, function() { let allMetaData = EXIF.getAllTags(this); console.log(allMetaData.Orientation); // 6 }); }; </script>
上面代碼的輸出 allMetaData.Orientation 的結果為 6 , 那 6 到底是什么意思呢? 可以參考這個篇文章 http://sylvana.net/jpegcrop/exif_orientation.html 里面有個表格:

如果這個表格看不太懂,再參考一下這篇文章 JPEG Orientation,里有個圖:
可以看出,攝像頭資訊是逆時針旋轉了 90 度,那要怎么糾正呢?就順時針旋轉 90 度抵消掉這個角度就好,
事實上, CropperJS 也會檢測圖片的 EXIF 資訊,并且會自動糾正角度的,詳情參考 https://github.com/fengyuanchen/cropperjs#checkorientation
這里也提到了,但只支持讀取 jpg 圖片的 EXIF 資訊,而我們這張圖片是 PNG 所以并不支持,
有個 CSS 屬性叫做 image-orientation , 它有個值叫做 from-image , 就是使用圖片的 EXIF 資料來旋轉的,可惜,目前 chrome 不支持該屬性,有興趣的可以了解一下,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/163653.html
標籤:JavaScript
上一篇:JS頁面跳轉加密解密URL引數
下一篇:JS---案例:美女時鐘
