轉(zhuǎn)載自林鑫的博客
前言
最近工作中遇到一個(gè)在前端上傳圖片后需求自動(dòng)調(diào)整圖片方向的需求峻仇,查找了一下資料,結(jié)合自己的工作總結(jié)如下邑商。
背景
通過網(wǎng)頁 input 標(biāo)簽上傳圖片摄咆,有一些使用手機(jī)拍攝的圖片會(huì)出現(xiàn)旋轉(zhuǎn)了90度的問題,包括 iPhone 和個(gè)別三星手機(jī)人断。這些手機(jī)豎著拍的時(shí)候才會(huì)出現(xiàn)這種問題吭从,橫拍出來的照片就正常顯示。因此恶迈,可以通過獲取手機(jī)拍照方向來對(duì)照片進(jìn)行旋轉(zhuǎn)涩金,從而解決這個(gè)問題谱醇。
Orientation
orientation,英文翻譯為方向步做,這個(gè)參數(shù)不是所有圖片都有副渴,但手機(jī)拍攝的照片是有的,所以我們需要在處理時(shí)判斷一下全度。
旋轉(zhuǎn)角度 | 參數(shù)值 |
---|---|
0° | 1 |
順時(shí)針90° | 6 |
逆時(shí)針90° | 8 |
180° | 3 |
參數(shù)為 1 的時(shí)候顯示正常煮剧,即手機(jī)橫拍顯示正常,Orientation = 1 将鸵,而手機(jī)豎拍的參數(shù)為 6勉盅。
想要獲取 Orientation 參數(shù),可以通過 exif.js 庫來操作顶掉。exif.js 功能很多草娜,體積也很大,未壓縮之前足足有 30k痒筒,這對(duì)手機(jī)頁面加載來說是非常大影響的宰闰。而我只需要獲取 Orientation 信息而已,所以這里使用原文作者精簡后exif.js 庫凸克,將代碼縮小到6KB议蟆。
exif.js 獲取 Orientation :
EXIF.getData(file, function() {
var Orientation = EXIF.getTag(this, 'Orientation');
});
其中file是通過input標(biāo)簽獲取的圖片闷沥,比如var file = $('#input').props.files[0]
萎战,而其中exif.js庫的原理是把file
通過FileReader
對(duì)象讀取出來,然后使用DataView
對(duì)象轉(zhuǎn)換成二進(jìn)制數(shù)組的格式舆逃,然后不斷讀取二進(jìn)制數(shù)據(jù)的字節(jié)信息蚂维,判斷出圖片的方向,返回一個(gè)封裝好的orientation值路狮。
旋轉(zhuǎn)
獲取到方向后虫啥,我們就要把圖片旋轉(zhuǎn)了,可能你會(huì)想到直接操作CSS的transfer rotate
方法奄妨,但對(duì)于加了預(yù)加載功能的圖片涂籽,這樣會(huì)在界面上看到一個(gè)旋轉(zhuǎn)的動(dòng)畫,實(shí)在不優(yōu)雅砸抛。所以我們使用canvas
來操作评雌,還能加入壓縮的功能,使圖片加載更快直焙。
ctx.rotate(angle)
景东,rotate 方法的參數(shù)為旋轉(zhuǎn)弧度。需要將角度轉(zhuǎn)為弧度:degrees * Math.PI / 180
奔誓,旋轉(zhuǎn)的中心點(diǎn)默認(rèn)都在 canvas 的起點(diǎn)斤吐,即 ( 0, 0 )。旋轉(zhuǎn)的原理如下圖:
旋轉(zhuǎn)之后,如果從 ( 0, 0 ) 點(diǎn)進(jìn)行 drawImage()和措,那么畫出來的位置就是在左圖中的旋轉(zhuǎn) 90 度后的位置庄呈,這時(shí)視圖不在可視區(qū)域了,而旋轉(zhuǎn)之后派阱,坐標(biāo)軸也跟著旋轉(zhuǎn)了抒痒,為了顯示在可視區(qū)域,需要將圖片點(diǎn)往 y 軸的反方向移 y 個(gè)單位颁褂,此時(shí)的起始點(diǎn)則為 ( 0, -y )故响。
同理,可以獲得旋轉(zhuǎn) -90 度后的起始點(diǎn)為 ( -x, 0 )颁独,旋轉(zhuǎn) 180 度后的起始點(diǎn)為 ( -x, -y )彩届。
壓縮
手機(jī)拍出來的照片太大,而且使用 base64 編碼的照片會(huì)比原照片大誓酒,那么在進(jìn)行預(yù)覽的時(shí)候進(jìn)行壓縮是有必要的≌寥洌現(xiàn)在的手機(jī)像素這么高,拍出來的照片寬高都有幾千像素靠柑,用 canvas 來渲染這照片的速度會(huì)相對(duì)比較慢啦桌。
因此第一步需要先對(duì)上傳照片的寬高做限制,判斷寬度或高度是否超出哪個(gè)范圍搔体,則等比壓縮其寬高剧腻。
var ratio = width / height;
if(imgWidth > imgHeight && imgWidth > xx){
imgWidth = xx;
imgHeight = Math.ceil(xx / ratio);
}else if(imgWidth < imgHeight && imgHeight > yy){
imgWidth = Math.ceil(yy * ratio);
imgHeight = yy;
}
第二步就通過 canvas.toDataURL()
方法來壓縮照片質(zhì)量。
canvas.toDataURL("image/jpeg", 1);
toDataURL() 方法返回一個(gè)包含圖片展示的 data URI 隔嫡。使用兩個(gè)參數(shù)甸怕,第一個(gè)參數(shù)為圖片格式,默認(rèn)為 image/png腮恩。第二個(gè)參數(shù)為壓縮質(zhì)量梢杭,在指定圖片格式為 image/jpeg 或 image/webp的情況下,可以從 0 到 1 的區(qū)間內(nèi)選擇圖片的質(zhì)量秸滴。
核心代碼
<input type="file" id="files" >
<img src="blank.gif" id="preview">
<script src="small-exif.js"></script>
<script>
var ipt = document.getElementById('files'),
img = document.getElementById('preview');
ipt.onchange = function (e) {
var file = e.target.files[0];
if(file){
EXIF.getData(file, function() {
// 先獲取方向
var Orientation = EXIF.getTag(this, 'Orientation');
// 因?yàn)槭窃诨卣{(diào)函數(shù)里設(shè)置orientation的值武契,所以要在回調(diào)成功后再讀取圖片,保持同步
var reader = new FileReader(),
image = new Image();
reader.onload = function (ev) {// 讀取文件
image.onload = function () {// 加載照片
var imgWidth = this.width,
imgHeight = this.height;
// 控制上傳圖片的寬高
if(imgWidth > imgHeight && imgWidth > 750){
imgWidth = 750;
imgHeight = Math.ceil(750 * this.height / this.width);
}else if(imgWidth < imgHeight && imgHeight > 1334){
imgWidth = Math.ceil(1334 * this.width / this.height);
imgHeight = 1334;
}
var canvas = document.createElement("canvas"),
ctx = canvas.getContext('2d');
canvas.width = imgWidth;
canvas.height = imgHeight;
if(Orientation && Orientation != 1){
switch(Orientation){
case 6: // 旋轉(zhuǎn)90度
canvas.width = imgHeight;
canvas.height = imgWidth;
ctx.rotate(Math.PI / 2);
ctx.drawImage(this, 0, -imgHeight, imgWidth, imgHeight);
break;
case 3: // 旋轉(zhuǎn)180度
ctx.rotate(Math.PI);
ctx.drawImage(this, -imgWidth, -imgHeight, imgWidth, imgHeight);
break;
case 8: // 旋轉(zhuǎn)-90度
canvas.width = imgHeight;
canvas.height = imgWidth;
ctx.rotate(3 * Math.PI / 2);
ctx.drawImage(this, -imgWidth, 0, imgWidth, imgHeight);
break;
}
}else{
ctx.drawImage(this, 0, 0, imgWidth, imgHeight);
}
img.src = canvas.toDataURL("image/jpeg", 0.8);
};
image.src = ev.target.result;// 這里一定要保證代碼的順序荡含,必須先創(chuàng)建image.onload咒唆,否則可能圖片一下就加載完了
};
reader.readAsDataURL(file);// 這里一定要保證代碼的順序,必須先創(chuàng)建reader.onload内颗,否則可能文件一下就讀取完了
});
}
}
</script>