什么是取景器
取景器是什么?取景器是相机的一个专业术语,在前端就是扫描拍照
取景器的实现原理
请求手机的一个媒体类型的视频轨道,利用一个div或者图片作为上层蒙层,然后在利用canvas绘制视频中某一帧的画面绘制为图片。
前期知识准备
- # MediaDevices.getUserMedia()
MediaDevices.getUserMedia() - Web API 接口参考 | MDN 在mdn中介绍了,这个api会调取摄像头,获取一个媒体轨道,这里我们只需要重点关注视频轨道,mdn对api的讲解也很清楚,下面是mdn的做好兼容的代码只需要直接使用
值得注意的点
- 该api必须https
- 兼容老版本浏览器
// 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
// 因为这样可能会覆盖已有的属性。这里我们只会在没有 getUserMedia 属性的时候添加它。
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function(constraints) {
// 首先,如果有 getUserMedia 的话,就获得它
var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// 一些浏览器根本没实现它 - 那么就返回一个 error 到 promise 的 reject 来保持一个统一的接口
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
}
// 否则,为老的 navigator.getUserMedia 方法包裹一个 Promise
return new Promise(function(resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
}
navigator.mediaDevices.getUserMedia(
{
audio: false, //不需要获取声音
video: {
//后置摄像头
facingMode: 'environment',
//分辨率,理想是2040,如果没有,最小1280,没有设置会比较模糊
width: { min: 1280, ideal: 2040 },
height: { min: 720, ideal: 1080 },
},
}
)
.then(function(stream) {
var video = document.querySelector('video');
// 旧的浏览器可能没有 srcObject
if ("srcObject" in video) {
video.srcObject = stream;
} else {
// 防止在新的浏览器里使用它,应为它已经不再支持了
video.src = window.URL.createObjectURL(stream);
}
video.onloadedmetadata = function(e) {
video.play();
};
})
.catch(function(err) {
console.log(err.name + ": " + err.message);
});
-
canvas绘图-drawImage
HTML5 canvas drawImage() 方法 canvas绘制,主要是要注意绘制过程中的参数,w3cschool也写的非常清楚,暂不做重复说明
案例源码
html部分
<div class="viewfinder">
<!-- 用于兼容,调用手机拍照功能 -->
<input id="file" type="file" accept="image/*" capture="camera" style="display:none">
<!--兼容ios的微信内置浏览器 -->
<button v-if="ioswx && status == 0" class="start" @click="start">开始</button>
<button v-if="status == 2" class="again" @click="butonClick(1)">重拍</button>
<button v-if="status == 2" class="finsh" @click="butonClick(2)">完成</button>
<div class="wrap-box">
<video playsinline style="height: 100vh;width: 100vw;position: fixed;top: 0;left: 0;object-fit: cover"/>
<div class="imgshow">
<img v-if="status==1" src="../../../public/images/2.png" class="box">
<img v-if="status==2" :src="imageUrl" alt="" class="wrapImg">
</div>
<div v-if="status == 1 " class="snap" @click="snapPhoto"/>
<canvas id="mycanvas" style="visibility: hidden;"/>
</div>
</div>
css部分
<style lang="less" scoped>
.wrap-box {
width: 100%;
position: fixed;
left: 0;
height: 100%;
.imgshow {
width: 100%;
position: absolute;
left: 0;
bottom: 25vh;
top: 25vh;
right: 0;
}
.box {
width: 100%;
height: 100%;
border: 10px solid rgba(0,0,0,0.5);box-sizing: border-box;
}
.wrapImg{
width:100%;
height: 100%;
}
.snap {
position: fixed;
left: 0;
top: 2vh;
z-index: 1000;
}
}
</style>
js逻辑部分
<script>
export default {
data() {
return {
status: 0, // 自定义相机-拍摄进度:0|未开启 1|开启但未拍摄 2|开启且已拍摄
imageUrl: '',
ioswx: false,
cameraShow: true,
};
},
mounted() {
this.ioswx = this.isWXandIos();
this.openCamera();
},
methods: {
isWXandIos() {
const aegent = navigator.userAgent.toLowerCase();
if (/micromessenger/.test(aegent) && /iPhone|mac|iPod|iPad/i.test(aegent)) {
return true;
}
return false;
},
start() {
const video = document.querySelector('video');
video.play();
this.status = 1;
},
async openCamera() {
const constraints = {
audio: false,
video: {
// facingMode: (this.front ? 'user' : 'environment'),
facingMode: 'environment',
width: { min: 1280, ideal: 2040 },
height: { min: 720, ideal: 1080 },
},
};
// 兼容
// 老的浏览器没实现mediaDevices
if (navigator.mediaDevices === undefined) navigator.mediaDevices = {};
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function(constraints) {
const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
}
return new Promise(function(resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
};
}
// 获取视频流
const that = this;
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
const video = document.querySelector('video');
video.srcObject = stream;
video.onloadedmetadata = function() {
video.play();
};
// 不是微信内置浏览器
if (!that.ioswx) {
that.status = 1;
}
})
.catch(function(err) {
console.log('nonononon', JSON.stringify(err));
// 调用原始摄像头;
that.originCamera();
});
},
originCamera() {
// const that = this;
const promise = new Promise(function(resolve, reject) {
const file = document.getElementById('file');
file.click();
console.log('fileclick');
file.onchange = function(event) {
if (!event) {
reject('empty');
}
const file = event.target.files[0];
resolve(file);
};
});
promise.then(value => {
console.log('vaue', value);
// that.submitPhoto('origin', value);
}
);
},
snapPhoto() {
console.log(111, 'paizhao');
const canvas = document.querySelector('#mycanvas');
const video = document.querySelector('video');
const width = canvas.width = video.videoWidth;
const height = canvas.height = video.videoHeight;
// let cut_y = height/4
// canvas.getContext('2d').drawImage(video, 0, cut_y, width, cut_y*2, 0, 0, width, height);
canvas.getContext('2d').drawImage(video, 0, 0, width, height);
this.imageFile = this.canvasToFile(canvas);
const p = new Promise(resolve => {
canvas.toBlob(blob => {
const url = URL.createObjectURL(blob);
resolve(url);
});
});
const that = this;
p.then(value => {
that.imageUrl = value;
that.status = 2;
});
},
canvasToFile(canvas) {
const dataurl = canvas.toDataURL('image/png');
let 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);
}
const file = new File([ u8arr ], 'phone.png', { type: mime });
return file;
},
// 提交
submitPhoto(type, file) {
if (type == 'origin') {
this.imageFile = file;
}
// 一系列的提交操作;
// console.log('提交', this.imageFile);
// // 在这里进行上传操作
// // let fd=new FormData()
// // fd.append("face_image",this.imageFile)
// // ...
// // 开始提示
},
butonClick(status) {
if (status == 1) {
this.status = 1;
return;
}
// 提交
},
},
};
</script>
注意一些细节功能
在ios的微信内置浏览器中,必须用户手动调用才会自动播放视频,因此兼容ios内置浏览器需加按钮让用户自动点击
整个功能就完结啦 参考以下文章