实现思路:
const constraints = {
audio: false,
video: {
// frameRate: {ideal: 20},
// cursor:'never',
// 'displaySurface':'monitor',
'facingMode': this.faceMode == 1 ? "user" : "environment",
// 'width': {ideal: 480 },
// 'height':{ideal: 640 }
}
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
video.srcObject = stream;
// 获取视频轨道
this.videoSteam = stream.getTracks();
video.play();
})
- 利用canvas 进行视频流截图获取到图片base64;
- videoMask.getBoundingClientRect();
- video.getBoundingClientRect();
canvas.width = maskPos.width;
canvas.height = maskPos.height;
const context = canvas.getContext('2d');
context.drawImage(video, -(videoPos.width - maskPos.width) / 2, -(videoPos.height - maskPos.height) / 2, videoPos.width, videoPos.height);
const base64Img = canvas.toDataURL("image/png
注意事项
- navigator.mediaDevices.getUserMedia,为浏览器获取媒体流的方法:
- localhost 域
- 开启了 HTTPS 的域
- 使用 file:/// 协议打开的本地文件 其他情况下,比如在一个 HTTP 站点上,navigator.mediaDevices 的值为 undefined。 如果想要 HTTP 环境下也能使用和调试 MediaDevices.getUserMedia(),可通过开启 Chrome 的相应参数。
jsdk封装好了直接上代码拿去玩撒。。。
(function(window){
class WebCamera {
constructor() {
this.viewBoxId=this.randomString();
this.faceMode=1;
this.videoSteam=null;
this.callback=null;
this.isShowFindFrame=2;
}
init(options) {
this.faceMode=options.faceMode||1;
this.callback=options.callback||null;
this.isShowFindFrame=options.isShowFindFrame||2;
if(!document.getElementById(this.viewBoxId)){
this.initViewDom();
}else{
const mask=document.getElementById('videoMask');
mask.style.display=this.isShowFindFrame==1?'block':'none';
this.open();
}
}
initViewDom() {
const videoWrap=this.createElement('div', {
id:this.viewBoxId,
styles:{
position: 'fixed',
left: '0px',
right: '0',
bottom: '0',
top: '0px',
width: '100%',
height: '100%',
display:'flex',
flexFlow:'column',
alignItems:'center',
justifyContent: 'center',
background:'#f2f4fa'
}
})
const videoBox=this.createElement('div',{
innerHTML:`<video
id=${this.viewBoxId+"_video"}
style="width:100%;height:auto; transform: rotateY(${this.faceMode==1?'180deg':'0deg'});"
autoplay="autoplay" playsinline="playsinline">
</video>`,
styles:{
width:'100%',
height:'100%',
position: 'relative',
backgroundColor: 'black',
overflow: 'hidden',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexFlow: 'column'
}
})
const height=(window.innerWidth<window.innerHeight)? window.innerWidth*9/16:window.innerHeight-20;
const width=(window.innerWidth<window.innerHeight)?window.innerWidth-20:window.innerHeight*16/9;
const mask=this.createElement('div', {
id:'videoMask',
styles:{
display:this.isShowFindFrame==1?'block':'none',
position:'absolute',
top:'50%',
left:'50%',
height:`${height}px`,
border:'1px solid #51de20',
width:`${width}px`,
boxSizing: 'border-box',
transform: 'translate(-50%, -50%)'
}
})
const videoBtn=this.createElement('div', {
id:'tick_video_Btn',
styles:{
width:'70px',
height:'70px',
borderRadius:'50%',
position: 'absolute',
bottom: '30px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
letterSpacing: '4px',
color:'white',
fontSize:'18px',
fontWeight:'bold',
border:'3px solid white',
padding:'2px'
},
innerHTML:'<div style="background:white; border:1px solid black;width: 100%;height: 100%;border-radius: 50%;"></div>',
onclick:(e)=>{
this.saveImg()
}
})
const videoBtnWrap=this.createElement('div', {
styles:{
width:'100%',
height:'100px',
position: 'absolute',
bottom: '0px',
display: 'flex',
justifyContent: 'center'
},
})
const domFrame=document.createDocumentFragment();
videoWrap.appendChild(videoBox);
videoWrap.appendChild(mask);
videoWrap.appendChild(videoBtnWrap);
videoBtnWrap.appendChild(videoBtn)
domFrame.append(videoWrap)
const errorBox=this.createElement('div', {id:`errorMsg_${this.viewBoxId}`})
document.body.appendChild(domFrame);
this.createVideo();
}
createVideo() {
navigator.mediaDevices.enumerateDevices().then(function(devices) {
console.log(devices,"00000000")
devices.forEach(function(device) {
console.log(device)
})
}).catch(e=>{
console.log(e)
})
const video= document.getElementById(this.viewBoxId+"_video");
const videoBtn=document.getElementById('tick_video_Btn');
const constraints={
audio: false,
video:{
frameRate: {ideal: 20}
}
}
navigator.mediaDevices.getUserMedia(constraints).then((stream)=> {
video.srcObject = stream;
console.log(video.srcObject)
this.videoSteam=stream.getTracks();
video.play();
}).catch((error)=> {
const errorConfig={
'NotAllowedError': '用户已禁止使用相机,请查看相关权限',
'PermissionDeniedError':'你还没有开启相机权限',
'AbortError':'其它异常',
'InvalidStateError':'拒绝异常',
'NotFoundError':'无法获取视频源',
'NotReadableError':'相机被占用',
'TypeError':'类型错误'
}
const message=errorConfig[error.name];
this.$message(message)
})
}
randomString = function (len) {
len = len || 32;
let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
let maxPos = $chars.length;
let pwd = '';
for (let i = 0; i < len; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
saveImg() {
const base64Str=this.getClipPath();
const orignBase64=this.getOriginPath();
if(this.callback){
this.callback({
clipPath:base64Str,
orignPath:orignBase64
});
}
this.closeVideo();
}
closeVideo(){
const videoId=this.viewBoxId;
const videoBox=document.getElementById(videoId);
const video=document.getElementById(`${videoId}_video`)
const tracks=this.videoSteam||[];
tracks.forEach(function(track) {
track.stop();
});
video.srcObject = null;
videoBox.style.display="none"
}
open(){
const videoId=this.viewBoxId;
const videoBox=document.getElementById(videoId);
videoBox.style.display='block';
this.createVideo();
}
getClipPath(){
let canvas=document.createElement("canvas");
const videoBoxId=this.viewBoxId+"_video";
const video = document.getElementById(videoBoxId);
const videoMask=document.getElementById('videoMask');
const maskPos=videoMask.getBoundingClientRect();
const videoPos=video.getBoundingClientRect();
canvas.width = maskPos.width;
canvas.height = maskPos.height;
const context= canvas.getContext('2d');
context.drawImage(video, -(videoPos.width-maskPos.width)/2,-(videoPos.height-maskPos.height)/2,videoPos.width,videoPos.height);
const base64Img=canvas.toDataURL("image/png");
canvas=null;
return base64Img;
}
getOriginPath(){
let videoCavans=document.createElement("canvas");
const videoBoxId=this.viewBoxId+"_video";
const video = document.getElementById(videoBoxId);
const videoPos=video.getBoundingClientRect();
videoCavans.width=videoPos.width;
videoCavans.height=videoPos.height;
const videoContxt=videoCavans.getContext('2d');
videoContxt.drawImage(video,0,0,videoPos.width,videoPos.height)
const orignBase64=videoCavans.toDataURL("image/png");
videoCavans=null;
return orignBase64
}
$message(msg) {
const messageBox = document.getElementById(`errorMsg_${this.viewBoxId}`)||document.body;
const mesageItem = this.createElement('div', {
className: 'errorBox',
styles:{
width: '238px',
height: '146px',
fontSize: '14px',
position:'absolute',
zIndex:'9999',
background:'white',
borderRadius:'14px',
boxSizing:'border-box',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
display:'flex',
justifyContent:'center',
flexFlow:'column',
alignItems:'center'
// padding: '9px 12px',
// background: '#fff',
// borderRadius: '8px',
// boxShadow: '0 6px 16px 0 rgb(0 0 0 / 8%), 0 3px 6px -4px rgb(0 0 0 / 12%), 0 9px 28px 8px rgb(0 0 0 / 5%)',
// pointerEvents: 'all',
// opacity: 0,
// animation: 'fadeOut 2s linear forwards'
},
})
const errorInfo=this.createElement('div', {
styles:{
fontSize: '14px',
fontFamily: 'PingFangSC-Regular, PingFang SC',
fontWeight: '400',
color: '#363A45',
lineHeight: '18px',
flex:1,
padding:'16px 16px 10px 16px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
},
innerHTML:msg
})
const closeBtn=this.createElement('div', {
styles:{
width: '100%',
height: '44px',
background: 'rgba(255,255,255,0)',
textAlign:'center',
color:'#3591F4',
borderTop: '1px solid #DDE6F0',
lineHeight: '44px',
fontSize:'17px',
cursor:'pointer'
},
onclick:(e)=>{
messageBox.removeChild(mesageItem);
this.closeVideo();
},
innerText:'确定'
})
mesageItem.appendChild(errorInfo);
mesageItem.appendChild(closeBtn);
messageBox.appendChild(mesageItem);
}
createElement(type,propertys){
const element=document.createElement(type);
for(let key in propertys){
switch(key.toLowerCase()){
case 'innertext':
element.innerText=propertys[key]
break;
case 'innerhtml':
element.innerHTML=propertys[key]
break;
case 'id':
element.id= propertys[key]
break;
case 'classname':
element.className= propertys[key]
case 'onclick':
element.onclick= propertys[key];
break;
case 'mouseDown':
element.onmousedown= propertys[key];
break;
case 'mouseMove':
element.onmousemove= propertys[key];
break;
case 'mouseUp':
element.onmouseup= propertys[key];
break;
case 'oninput':
element.oninput= propertys[key];
break;
case 'styles':
for(let name in propertys[key]){
element.style[name]=propertys[key][name];
}
break;
default:
element.setAttribute(key,propertys[key]);
}
}
return element;
}
}
window.testWebCamera=new WebCamera()
}(window))
界面调用demo
<!DOCTYPE html>
<html>
<head>
<title>拍照</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/eruda/2.4.1/eruda.min.js"></script>
<script src="./camera.js"></script>
<script>
eruda.init();
</script>
</head>
<style>
body {
margin: 0;
padding: 0;
}
html{
margin: 0;
padding: 0;
}
</style>
<body >
<button onclick="testOpen()">拍照</button>
<div onclick="openFile()">打开文件</div>
</body>
<script>
async function openFile(){
const pickerOpts = {
types: [
{
description: "Images",
accept: {
"image/*": [".png", ".gif", ".jpeg", ".jpg"],
},
},
],
excludeAcceptAllOption: true,
multiple: false,
};
let fileHandle;
[fileHandle] = await window.showOpenFilePicker(pickerOpts);
console.log(fileHandle);
}
function testOpen(){
testWebCamera.init({
isShowFindFrame:1,
faceMode:1,
callback:(result)=>{
console.log(result)
}
})
}
</script>
</html>