本文講的圖片上傳,主要是針對上傳頭像的。你們都知道,上傳頭像通常都會分紅如下 4 個步驟:css
選擇圖片 -> 預覽圖片 -> 裁剪圖片 -> 上傳圖片html
接下來,就詳細的介紹每一個步驟具體實現。前端
選擇圖片有什麼好講的呢?不就一個 input[type=file]
,而後點擊就能夠了嗎?確實是這樣的,可是,咱們想要作得更加的友好一些,好比須要過濾掉非圖片文件, 或只容許從攝像頭拍照獲取圖片等,仍是須要進行一些簡單配置的。git
下面就先來看看最簡單的選擇圖片:github
<input type="file" />
複製代碼
這時候,點擊這個 input , 在 iOS 手機的顯示以下:ajax
其中的 「瀏覽」 選項,能夠查看到非圖片類型的文件,這並非咱們想要的結果,畢竟咱們只想要圖片類型。能夠經過 accept
屬性來實現,以下:chrome
<input type="file" accept="image/*">
複製代碼
這樣就能夠過濾掉非圖片類型了。可是圖片的類型可能也太多了, 有些可能服務器不支持,因此,若是想保守一些,只容許 jpg
和 png
類型,能夠寫成這樣:canvas
<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
階段, 可是大多數的現代瀏覽器都已經良好的支持了。 下面就介紹一下如何使用這兩個方法。
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(),
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 指定的內容尚未生成好,應該是異步生成的,而後才渲染出來的。因此並無很好的辦法來對比他們的性能。
若是想要學習更多關於圖片預覽,能夠閱讀如下兩篇文章:
關於圖片的裁剪,很天然的會想到使用 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 = new FormData();
formData.append('avatar', blob);
fetch('xxxx', {
method: 'POST',
body: formData
});
});
}
複製代碼
這段代碼並不能真正執行,由於咱們尚未對應的後端服務器。若是想要嘗試上傳圖片的朋友,能夠參考一下這篇文章 寫給新手前端的各類文件上傳攻略,從小圖片到大文件斷點續傳,因爲篇幅緣由,這裏就不展開啦。
關於圖片上傳的介紹,差很少不到些結束了。可是以前在 iPhone 和 小米 手機上,遇到一個奇怪的問題: 就是我使用前置攝像頭自拍出來的照片,選擇以後 ,會自逆時針旋轉 90 度,好比像下圖:
拍照的時候明明就是正着拍的,爲何預覽就會變成橫着了呢?當時第一次遇到這個問題的時候,也以爲好奇怪。後來查了一下,得知這是由於拍照時,相機都會記錄拍照的角度信息,可能 iPhone 前置攝像頭記錄的角度信息和其餘的有點不同,而 iPhone 本身的相冊在瀏覽照片時,自動糾正了角度 ,而瀏覽器卻沒有糾正,因此纔會出現這個旋轉。
爲了解決這個問題,須要使用 EXIF 這個庫來處理。
我剛剛試了一下,發現個人 iPhone 如今居然不會有這個問題了,大概是半年前,當時在作一個需求時,自拍的圖片會發生這種旋轉,有多是 iOS 系統升級後, 已經修復了這個問題。而如今身邊又沒有小米手機, 因此也很差復現。還好,當時我保存了一張會自動旋轉的圖片。你們能夠到這裏下載:
這圖片下載後,用電腦的圖片查看器打開是正常的,可是,在瀏覽器中,選擇這個圖片後,使用 URL.createObjectURL()
或 FileReader
來預覽就會發生旋轉。甚至直接 img 標籤引入也會逆時針旋轉了 90 度,好比:
<img src="https://ok.166.net/gameyw-misc/opd/squash/20191028/170829-f5t38i0d9k.png">
複製代碼
效果以下:
下面就以這張圖片爲例,介紹一下如何使用 EXIF 來檢測圖片角度。關於 EXIF 的詳細用法你們能夠到 github 的主頁上查看 github.com/exif-js/exi…
<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 究竟是什麼意思呢? 能夠參考這個篇文章 Exif Orientation Tag 裏面有個表格:
若是這個表格看不太懂,再參考一下這篇文章 JPEG Orientation,裏有個圖:
能夠看出,攝像頭信息是逆時針旋轉了 90 度。那要怎麼糾正呢?可使用 CSS 的 transfrom: rotate(-90deg)
順時針旋轉 90 度抵消掉這個角度就好。
事實上, CropperJS
也會檢測圖片的 EXIF 信息,而且會自動糾正角度的,詳情參考 github.com/fengyuanche… 這裏也提到了,但只支持讀取 jpg 圖片的 EXIF 信息,而咱們這張圖片是 PNG 因此並不支持。
有個 CSS 屬性叫作 image-orientation , 它有個值叫作 from-image
, 就是使用圖片的 EXIF 數據來旋轉的。惋惜,目前 chrome 不支持該屬性。有興趣的能夠了解一下。
好啦,就先寫到這裏啦,有問題的歡迎在評論區交流哈~
PS: 已經有大半年沒有更新過文章了,先定個小目標,之後每兩個星期更新一篇。