這周產(chǎn)品提出了新的需求整葡,要求前端H5頁面調(diào)起移動設(shè)備攝像頭,并實現(xiàn)拍照功能讥脐。完成之后來記錄一下開發(fā)經(jīng)歷遭居,希望對之后遇到同樣問題開發(fā)者有所幫助!
首先H5要調(diào)起設(shè)備攝像頭需要使用 input 標(biāo)簽攘烛,借助標(biāo)簽的 capture 屬性來完成調(diào)起操作。上代碼:
<label>照相機(jī)</label>
<input type="file" id='image' accept="image/*" capture='camera'>
<label>圖片多選</label>
<input type="file" accept="image/*" multiple>
<label>調(diào)起前置攝像頭</label>
<input type="file" accept="image/*" capture="user">
拍照完成之后镀首,需要讀取文件坟漱,這就需要使用 FileReader 對象來完成相應(yīng)操作。上代碼:
// 創(chuàng)建 FileReader 對象
var reader = new FileReader();
reader.onload = function() {
that.compress(this.result, file);
};
reader.readAsDataURL(file);
this.fileUrl = window.URL.createObjectURL(file);
// this.result 既是讀取文件結(jié)果更哄,是一個Base64形式的文件流芋齿,類似data:image/png;base64,*****
// file 是獲去到的文件對象
file.png
that.compress 這個方法是用來處理圖片壓縮,是否旋轉(zhuǎn)等功能成翩。
this.fileUrl 是用來做 選定照片 / 拍攝照片 回顯觅捆,就是 img 的 src 屬性值。
到這里就可以實現(xiàn)簡單的H5調(diào)起相冊麻敌、攝像頭操作栅炒。但是測試的時候會發(fā)現(xiàn)像素好的手機(jī)拍出來的照片非常大,就造成了上傳接口相應(yīng)超時問題,此時不要慌赢赊,接下來就說一說關(guān)于照片壓縮問題乙漓。
這里的圖片壓縮就需要 canvas 來配合實現(xiàn)。
let that = this;
var width, height;
var MAX_WH = 800;
var image = new Image();
image.onload = function() {
if (image.height > MAX_WH) {
// 寬度等比例縮放 *=
image.width *= MAX_WH / image.height;
image.height = MAX_WH;
}
if (image.width > MAX_WH) {
// 寬度等比例縮放 *=
image.height *= MAX_WH / image.width;
image.width = MAX_WH;
}
//壓縮
var quality = 80;
var cvs = document.createElement("canvas");
var context = cvs.getContext("2d");
cvs.width = width = image.width;
cvs.height = height = image.height;
switch (orientation) {
case 6:
case 8:
cvs.width = height;
cvs.height = width;
break;
}
context.clearRect(0, 0, cvs.width, cvs.height);
context.drawImage(image, 0, 0, image.width, image.height);
that.readerResult = cvs.toDataURL("image/jpeg", quality / 100);
that.getBankcardFn(); // 調(diào)用上傳接口
};
image.src = res;
首先释移,創(chuàng)建Image對象叭披,給image的src屬性賦值加載完之后,調(diào)用onload玩讳。在onload中進(jìn)行圖片的壓縮操作涩蜘。
cvs.toDataURL() 方法返回的就是壓縮之后的圖片的 Base64 編碼,這時候就可以把編碼上傳至服務(wù)器了熏纯。
到了這里已經(jīng)就完成了一大半同诫,功能已經(jīng)基本實現(xiàn),現(xiàn)在就可以開始考慮優(yōu)化豆巨、提高用戶體驗了剩辟。經(jīng)過測試,會發(fā)現(xiàn)iOS部分機(jī)型會莫名造成圖片旋轉(zhuǎn)往扔,不要慌贩猎。
這里搭配EXIF對象來拿到圖片的原信息。
var orientation = 0;
EXIF.getData(file, function() {
orientation = EXIF.getTag(file, "Orientation");
});
//解決ios圖片旋轉(zhuǎn)問題
switch (orientation) {
//iphone橫屏拍攝萍膛,此時home鍵在左側(cè)
case 3:
// 180度向左旋轉(zhuǎn)
context.translate(width, height);
context.rotate(Math.PI);
break;
//iphone豎屏拍攝吭服,此時home鍵在下方(正常拿手機(jī)的方向)
case 6:
context.rotate(0.5 * Math.PI);
context.translate(0, -height);
break;
//iphone豎屏拍攝,此時home鍵在上方
case 8:
// 逆時針旋轉(zhuǎn)90度
context.rotate(-0.5 * Math.PI);
context.translate(-width, 0);
break;
}
這里的EXIF對象是(Exchangeable Image File)是“可交換圖像文件”的縮寫蝗罗,當(dāng)中包含了專門為數(shù)碼相機(jī)的照片而定制的元數(shù)據(jù)艇棕,可以記錄數(shù)碼照片的拍攝參數(shù)、縮略圖及其他屬性信息串塑,簡單來說沼琉,Exif信息是鑲嵌在 JPEG/TIFF 圖像文件格式內(nèi)的一組拍攝參數(shù),需要注意的是EXIF信息是不支持png,webp等圖片格式的桩匪。
可以引入CDN 也可以 npm install exif-js --save
到了這里功能就可以交付了打瘪。下面附上完整代碼粘貼即用:
<!-- 詳情操作頁面 -->
<template>
<div class="wrapper Detial">
<div class="title">
{{ this.$route.query.group }}
</div>
<div class="content">
<div v-if="!mutually" class="cont">
<input @change="fileChoose" type="file" id="image" accept="image/*" />
上傳銀行卡照片
<img src="../assets/upload.png" alt="" />
</div>
<div v-if="mutually" class="conte">
<img :src="fileUrl" alt="" />
</div>
</div>
<div class="inputGroup">
<label v-for="(item, index) in detialData" :key="index">
<span class="text">{{ item.name }}</span>
<span class="cardNumber">{{ item.value }}</span>
</label>
</div>
<div class="button">
<button @click="getBactFn" class="btn">返回首頁</button>
</div>
<div v-if="maskType" class="mask box">
<div class="loader-15"></div>
<span class="ideng">正在識別,請稍后傻昙!</span>
</div>
</div>
</template>
<script>
import EXIF from "exif-js";
export default {
name: "Detial",
data() {
return {
fileUrl: null,
mutually: false,
readerResult: "",
detialData: [],
maskType: false
};
},
//生命周期 - 創(chuàng)建完成(訪問當(dāng)前this實例)
created() {},
//生命周期 - 掛載完成(訪問DOM元素)
mounted() {},
//存放自定義方法
methods: {
// 讀取圖片
fileChoose(ifile) {
this.maskType = true;
this.mutually = true;
let that = this;
let file = ifile.target.files[0];
console.log(file);
var reader = new FileReader();
reader.onload = function() {
that.compress(this.result, file);
};
reader.readAsDataURL(file);
this.fileUrl = window.URL.createObjectURL(file);
},
// 調(diào)用OCR銀行卡識別接口
getBankcardFn() {
const params = {
image_data: this.readerResult,
detect_direction: "true"
};
this.$api.OCRServerDetial.getBankcardPort(params).then(res => {
if (res.code === 200) {
this.maskType = false;
this.detialData = [
{
name: "銀行卡號:",
value: res.result.bank_card_number
},
{
name: "有 效 期:",
value: res.result.valid_date
},
{
name: "銀行名稱:",
value: res.result.bank_name
},
{
name: "卡片類型:",
value:
res.result.bank_card_type == 0
? "不能識別"
: res.result.bank_card_type == 1
? "借記卡"
: "信用卡"
}
];
} else if (res.code === -1) {
this.maskType = false;
this.mutually = false;
alert("請上傳銀行卡照片,或?qū)D片旋轉(zhuǎn)90°重試!");
}
});
},
// 壓縮圖片
compress(res, file) {
let that = this;
var orientation = 0;
if (file && /^image\//i.test(file.type)) {
EXIF.getData(file, function() {
orientation = EXIF.getTag(file, "Orientation");
});
var width, height;
var MAX_WH = 800;
var image = new Image();
image.onload = function() {
if (image.height > MAX_WH) {
// 寬度等比例縮放 *=
image.width *= MAX_WH / image.height;
image.height = MAX_WH;
}
if (image.width > MAX_WH) {
// 寬度等比例縮放 *=
image.height *= MAX_WH / image.width;
image.width = MAX_WH;
}
//壓縮
var quality = 80;
var cvs = document.createElement("canvas");
var context = cvs.getContext("2d");
cvs.width = width = image.width;
cvs.height = height = image.height;
switch (orientation) {
case 6:
case 8:
cvs.width = height;
cvs.height = width;
break;
}
//解決ios圖片旋轉(zhuǎn)問題
switch (orientation) {
//iphone橫屏拍攝闺骚,此時home鍵在左側(cè)
case 3:
// 180度向左旋轉(zhuǎn)
context.translate(width, height);
context.rotate(Math.PI);
break;
//iphone豎屏拍攝,此時home鍵在下方(正常拿手機(jī)的方向)
case 6:
context.rotate(0.5 * Math.PI);
context.translate(0, -height);
break;
//iphone豎屏拍攝妆档,此時home鍵在上方
case 8:
// 逆時針旋轉(zhuǎn)90度
context.rotate(-0.5 * Math.PI);
context.translate(-width, 0);
break;
}
context.clearRect(0, 0, cvs.width, cvs.height);
context.drawImage(image, 0, 0, image.width, image.height);
that.readerResult = cvs.toDataURL("image/jpeg", quality / 100);
that.getBankcardFn(); // 調(diào)用上傳接口
};
image.src = res;
}
},
// 點(diǎn)擊返回首頁
getBactFn() {
this.$router.push({ path: "/ocrList" });
}
},
//生命周期 - 頁面銷毀前
beforeDestroy() {}
};
</script>
<style lang="scss" scoped>
/* @import url(); 引入css類 */
.mask {
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
position: absolute;
top: 0;
left: 0;
}
.box {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 30px;
color: rgba(200, 200, 200, 1);
padding: 0 1em;
-webkit-transition: 0.3s color, 0.3s border;
transition: 0.3s color, 0.3s border;
box-sizing: border-box;
}
[class*="loader-"] {
display: inline-block;
width: 1em;
height: 1em;
color: inherit;
vertical-align: middle;
pointer-events: none;
}
.ideng {
font-size: 0.3rem;
margin-top: 0.5rem;
}
.loader-15 {
background: currentcolor;
position: relative;
-webkit-animation: loader-15 1s ease-in-out infinite;
animation: loader-15 1s ease-in-out infinite;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
width: 0.25em;
height: 0.5em;
}
.loader-15:after,
.loader-15:before {
content: "";
position: absolute;
width: inherit;
height: inherit;
background: inherit;
-webkit-animation: inherit;
animation: inherit;
}
.loader-15:before {
right: 0.5em;
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
.loader-15:after {
left: 0.5em;
-webkit-animation-delay: 0.6s;
animation-delay: 0.6s;
}
@-webkit-keyframes loader-15 {
0%,
100% {
box-shadow: 0 0 0 currentcolor, 0 0 0 currentcolor;
}
50% {
box-shadow: 0 -0.25em 0 currentcolor, 0 0.25em 0 currentcolor;
}
}
@keyframes loader-15 {
0%,
100% {
box-shadow: 0 0 0 currentcolor, 0 0 0 currentcolor;
}
50% {
box-shadow: 0 -0.25em 0 currentcolor, 0 0.25em 0 currentcolor;
}
}
.button {
margin-top: 0.5rem;
text-align: center;
.btn {
color: #39a1ec;
background: none;
border: 1px solid #ccc;
}
}
.title {
font-size: 0.4rem;
color: #333333;
padding: 0.2rem;
}
.content {
width: 100%;
height: 4.5rem;
position: relative;
box-sizing: border-box;
padding: 0 0.2rem 0.6rem;
border-bottom: 0.3rem solid #f5f5f5;
.conte {
width: 100%;
height: 100%;
border-radius: 8px;
overflow: hidden;
img {
width: 100%;
height: 100%;
display: block;
}
}
.cont {
width: 100%;
height: 100%;
color: #333;
text-align: center;
line-height: 3.6rem;
font-size: 0.4rem;
background: url("../assets/5.png") no-repeat;
background-size: 100% 100%;
img {
width: 0.7rem;
height: 0.5rem;
}
input {
width: 100%;
height: 100%;
outline: none;
opacity: 0;
position: absolute;
top: 0;
left: 0;
}
}
}
.inputGroup {
padding: 0 0.2rem;
label {
display: flex;
padding: 0.24rem 0;
align-items: center;
justify-content: center;
border-bottom: 1px solid #eeeeee;
span {
font-size: 0.3rem;
color: #333333;
font-family: STHeitiSC-Medium;
}
.text {
width: 1.6rem;
color: #999999;
margin-right: 0.1rem;
}
.cardNumber {
flex: 1;
padding-left: 0.3rem;
}
}
}
</style>