拍摄身份证时,相机显示身份证正反面轮廓图
项目需求是牌神身份证时在用户摄像机界面需要添加身份证正反面轮廓图,当时项目使用的是vue2+vant的h5项目,vant不支持修改相机,此时想起用原生方式(navigator.mediaDevices.getUserMedia),掉摄像机的api
完整代码如下显示:
<template>
<div>
<!-- capture="environment" -->
<!-- :after-read="afterRead" -->
<van-uploader v-if="this.deviceType === 'PC端'"
accept="image/*"
:capture = "cameraTag?'camera':null"
:preview-image="false"
:max-count="1"
:disabled="disabled"
:before-read="screenshot ? uploadChange : ''"
:after-read="screenshot ? '' : afterRead"
>
<div class="upload_button">
<van-loading v-show="loading" class="loading" />
<img class="show_img" v-if="value" :src="value" alt="" />
<template v-else>
<div class="camera_icon">
<svg-icon iconClass="camera"></svg-icon>
<p>{{ imgText }}</p>
</div>
<img
:style="`width:${width};height:${height};`"
:src="buttonImage"
alt=""
/>
</template>
</div>
</van-uploader>
<div class="image-container" @click="takePictures" ref="openCamera" v-if="this.deviceType === '手机端'">
<img class="show_img" v-if="value" :src="value" alt="" style="width: 100%;"/>
<img v-else
style="width:100%;height:100%;"
:src="buttonImage"
alt=""
/>
</div>
<div class="v-cropper-layer" ref="layer">
<div class="layer-header">
<button class="cancel" @click="cancelHandle">取消</button>
<button class="confirm" @click="confirmHandle">裁剪</button>
</div>
<img ref="cropperImg" />
</div>
<!-- 弹出相机或相册 -->
<van-popup v-model="showPicker" position="bottom">
<section class="sectPopup">
<div @click="takePictures">拍摄</div>
<div class="xiangce">
从相册选择
<label for="fileupload" class="custom-file-upload">
选择文件
</label>
<input type="file" id="fileupload" size="100" accept="image/*" capture="environment" @change="handleFileUpload"/>
</div>
<div @click="showPicker = false">取消</div>
</section>
</van-popup>
<!-- 弹出相机 -->
<van-popup v-model="show" position="bottom" :style="{ width: '100%', height: '100%' }">
<div class="header">
<van-nav-bar class="title" left-arrow title="身份证拍照" :fixed="true" @click-left="closeCamera" />
</div>
<div id="cameraContainer" style="height: 100%; overflow-x: hidden;">
<div class="container" style="height: 100%; overflow-x: hidden;">
<video style="height: 100%;overflow-x: hidden;" ref="video" id="video-fix" autoplay webkit-playsinline playsinline class="camera-video"></video>
<div class="photograph" @click="takePhoto">
<div id="captureButton" >
<div class="cap-inner"></div>
</div>
</div>
<!-- 正反面轮廓图 -->
<div class="front">
<img v-if="buttonImage.includes('renxiang')" src="@/assets/images/zheng.png" alt="">
<img v-else src="@/assets/images/fan.png" alt="">
</div>
</div>
</div>
</van-popup>
</div>
</template>
<script>
import $api from "@/api/index";
import { Toast } from "vant";
import Compressor from 'compressorjs';
import Cropper from "cropperjs";
import "cropperjs/dist/cropper.min.css";
export default {
props: {
value: {
type: String,
default: "",
},
width: {
type: String,
default: "100%",
},
height: {
type: String,
default: "100%",
},
buttonImage: {
type: String,
default: require("@/assets/images/ic_other@1x.png"),
},
imgText: {
type: String,
default: "",
},
disabled: {
type: Boolean,
default: false,
},
cameraTag: {
type: Boolean,
default: false,
},
screenshot: {
type: Boolean,
default: false,
},
},
data() {
return {
loading: false,
cropper: {},
filename: "",
show: false,
showPicker: false,
deviceType: null
};
},
mounted() {
this.init();
//根据不同路由跳转不同页面
if( this.isMobile() ){
console.log("手机端")
this.deviceType = '手机端'
Toast('手机端')
}else{
console.log("PC端")
this.deviceType = 'PC端'
Toast('PC端')
}
},
methods: {
// 校验当前操作环境是pc还是移动
isMobile(){
let flag = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)
return flag
},
async openCamera() {
this.showPicker = true
},
// 打开相机摄像头
async takePictures() {
this.showPicker = false
this.show = true;
const stream = await navigator.mediaDevices.getUserMedia({video: { facingMode: "environment" }})
const videoRef = this.$refs.video;
videoRef.srcObject = stream
},
// 选择文件进行上传
handleFileUpload(event) {
console.log('this.index', this.index);
this.showPicker = false
var that = this
const file = event.target.files[0]; // 获取选择的文件
new Compressor(file, {
quality: 0.6,
async success(result) {
const params = new FormData();
params.append("file", result, result.name);
const resultData = await $api.tool.uploadFile(params);
if (resultData.success) {
that.showPicker = false
that.$emit("change", resultData.data.filePath, result);
console.log('that.index', that.index);
}
},error(err) {
console.error(err.message);
}
})
},
// 关闭摄像头
closeCamera() {
const video = this.$refs.video;
if (video && video.srcObject) {
const tracks = video.srcObject.getTracks();
tracks.forEach((track) => track.stop());
}
this.show = false;
},
// 拍照
async takePhoto() {
var that = this
const video = this.$refs.video;
const canvas = document.createElement('canvas');
if (video && canvas) {
const context = canvas.getContext('2d');
if (context) {
// 设置画布尺寸与视频一致
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
context.drawImage(video, 0, 0);
// 将图像数据转换为二进制格式
const blob = await this.dataURLToBlob(canvas.toDataURL('image/png'));
// 将 Blob 转换为 File 对象
const file = new File([blob], 'photo.png', { type: 'image/png' });
// 创建 FormData 对象并添加图像数据
const formData = new FormData();
formData.append('file', blob, '001.png');
// 发送请求
const resultData = await $api.tool.uploadFile(formData);
if (resultData.success) {
that.$emit("change", resultData.data.filePath, file);
}
Toast.success('拍照成功');
this.closeCamera();
}
}
},
// 将 Data URL 转换为 Blob
dataURLToBlob(dataURL) {
const byteString = atob(dataURL.split(',')[1]);
const mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0];
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ab], { type: mimeString });
},
// 上传
async afterRead(file) {
this.compressImage(file.file,this)
// const params = new FormData();
// params.append("file", file.file);
// this.loading = true;
// const result = await $api.tool.uploadFile(params);
// if (result.success) {
// this.$emit("change", result.data.filePath, file);
// }
this.loading = false;
},
compressImage(file,zone) {//作用于变了
new Compressor(file, {
quality: 0.6,
async success(result) {
// result.name=file.name
console.log("result********1" + result.name)
const params = new FormData();
// params.append("file", result, result.name);
params.append("file", file);
console.log('params', params);
const resultData = await $api.tool.uploadFile(params);
if (resultData.success) {
zone.$emit("change", resultData.data.filePath, result);
}
},error(err) {
console.error(err.message);
},
});
},
// 截图上传
confirmHandle(file) {
let cropBox = this.cropper.getCropBoxData();
let cropCanvas = this.cropper.getCroppedCanvas({
width: cropBox.width,
height: cropBox.height,
});
cropCanvas.toBlob(async (imgData) => {
const params = new FormData();
let fileImg = new window.File([imgData], this.filename, {
type: "image/jpeg",
});
params.append("file", fileImg);
this.loading = true;
const result = await $api.tool.uploadFile(params);
if (result.success) {
this.$emit("change", result.data.filePath, file);
this.cancelHandle();
}
this.loading = false;
}, "image/jpeg");
},
// 初始化裁剪插件
init() {
let cropperImg = this.$refs["cropperImg"];
this.cropper = new Cropper(cropperImg, {
dragMode: "move",
});
},
// 选择上传文件
uploadChange(e) {
console.log(e, "select");
let file = e;
this.filename = file["name"];
let URL = window.URL || window.webkitURL;
this.$refs["layer"].style.display = "block";
this.cropper.replace(URL.createObjectURL(file));
},
// 取消上传
cancelHandle() {
this.cropper.reset();
this.$refs["layer"].style.display = "none";
// this.$refs["file"].value = "";
},
// base64转file
dataURLtoBlob(dataurl) {
var arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
},
},
};
</script>
<style lang="scss" scoped>
.camera_icon {
width: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 10vw;
color: $common-color;
text-align: center;
p {
font-size: 4vw;
margin: 0;
}
}
.upload_button {
width: 45vw;
height: 30vw;
.loading {
position: absolute;
transform: translate(50%, -50%);
top: 50%;
right: 50%;
}
.show_img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 1vw;
}
}
// .v-simple-cropper {
// .file {
// display: none;
// }
.v-cropper-layer {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: #fff;
z-index: 99999;
display: none;
.layer-header {
position: absolute;
top: 0;
left: 0;
z-index: 99999;
background: #fff;
width: 100%;
height: 11vw;
padding: 0 2vw;
box-sizing: border-box;
}
.cancel,
.confirm {
line-height: 10vw;
font-size: 4vw;
background: inherit;
border: 0;
outline: 0;
float: left;
}
.confirm {
float: right;
}
img {
position: inherit !important;
border-radius: inherit !important;
float: inherit !important;
}
}
.container {
position: relative;
height: 100%;
width: 100%;
.photograph {
position: absolute;
width: 20vw;
height: 20vw;
border-radius: 50%;
background: #fff;
z-index: 10;
left: 50%;
transform: translateX(-50%);
bottom: 8vw;
}
.front {
width: 80%;
height: 70%;
position: absolute;
bottom: calc(8vw + 20vw + 8vw);
left: 50%;
transform: translateX(-50%);
img {
width: 100%;
height: 100%;
}
}
}
.sectPopup {
display: flex;
flex-direction: column;
align-items: center;
line-height: 13vw;
font-size: 4vw;
div {
width: 100%;
text-align: center;
}
.xiangce {
border-top: 0.5vw solid #e7e5e5;
width: 100%;
border-bottom: 3vw solid #f1f1f1;
position: relative;
overflow: hidden;
.custom-file-upload {
display: inline-block;
padding: 1.33333vw 2.66667vw;
cursor: pointer;
border-radius: 0.53333vw;
color: transparent;
font-weight: bold;
width: 100%;
position: absolute;
left: 0;
}
input[type="file"] {
display: none;
}
}
}
.image-container {
height: 27vw;
overflow: hidden;
position: relative;
.image-container img {
position: absolute;
top: 50%;
left: 50%;
width: auto;
height: auto;
min-width: 100%;
min-height: 100%;
transform: translate(-50%, -50%);
}
}
#captureButton {
position: absolute;
bottom: 5vw;
left: 50%;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
background-color: rgb(241 241 241 / 80%);
border-radius: 50%;
width: 30vw;
height: 30vw;
max-width: 10vw;
max-height: 10vw;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
cursor: pointer;
}
::v-deep .van-dialog {
height: 100%;
}
::v-deep .van-dialog__content {
height: 100%;
}
::v-deep .van-nav-bar{
background: transparent !important;
}
::v-deep .van-nav-bar__title {
color: #fff;
}
// }
</style>