前言:上头有要求让我尝试是否可以写一个“不依赖html5plus”的拍照功能用在微信浏览器,所以我就试着整了一个demo,结果而言并不能在微信浏览器中使用。
关于demo:本文提供 【在线版】 和 【angular8的ts版】。说实话,个人总结这个demo也就图一乐(总算有契机去初识video标签了),windows笔记本里开浏览器跑会调笔记本摄像头,但在手机的浏览器里面上只可以调前置摄像头,没去深入研究手机端如何调后置摄像头
在线版代码:
.html
<button (click)="handleVideo(true)" *ngIf="cameraStatus.open===false" id="openCapture">打开摄像头</button>
<div class="capture_box" style="position: relative;
width: 640px;
height: 480px;
display: flex;
justify-content: center;" *ngIf="cameraStatus.open===true">
<div class="capture_buttonsbox">
<div class="capture_cancel">
<a class="arrow-icon" (click)="handleVideo(false)" id="capture_cancelbtn">
<span class="left-bar"></span>
<span class="right-bar"></span>
<span class="three-bar"></span>
</a>
</div>
<div class="capture_confirm1" (click)="handleCapture()" id="capture">
<div class="capture_confirm2"></div>
</div>
</div>
<video id="camera_video" style="width: 100%;
height: 100%;
border: 2px blue solid;
z-index: 1000;"></video>
</div>
<br />
<img src="" id="camera_show" alt="暂无照片" title="photo">
.css
.capture_buttonsbox {
height: 80px;
width: 100%;
position: absolute;
bottom: 16px;
z-index: 1001;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.capture_confirm1 {
background: linear-gradient(#F7F2F6, #B2AC9E);
box-shadow: 0 0 8px rgba(0, 0, 0, 0.3), 0 5px 5px rgba(0, 0, 0, 0.5);
border-radius: 24px;
height: 50px;
width: 50px;
display: inline-flex;
justify-content: center;
align-items: center;
position: absolute;
bottom: 16px;
z-index: 1001;
}
.capture_confirm1:hover {
cursor: pointer;
}
.capture_confirm1:active {
box-shadow: 0 0 8px rgba(0, 0, 0, 0.3), 0 5px 5px rgba(0, 0, 0, 0.2);
}
.capture_confirm2 {
height: 30px;
width: 30px;
border-radius: 24px;
background: linear-gradient(#CBC7BC, #D2CBC3);
}
.capture_cancel {
/*background-color: green;*/
width: 50%;
align-self: flex-start;
display: inline-flex;
justify-content: center;
align-items: center;
left: 0;
}
.arrow-icon {
/* background-color: lightcoral; */
background-color: transparent;
height: 52px;
width: 58px;
position: relative;
cursor: pointer;
border-radius: 4px;
transform-origin: center center;
transform: rotate(90deg);
clear: both;
}
.left-bar {
position: absolute;
background-color: transparent;
top: 22px;
left: 0;
width: 40px;
height: 8px;
display: block;
transform: rotate(60deg);
float: right;
border-radius: 4px;
}
.left-bar:after {
content: "";
background-color: #F7F2F6;
width: 40px;
height: 8px;
display: block;
float: right;
border-radius: 10px;
z-index: -1;
box-shadow: 0px -2px 2px rgba(0, 0, 0, 0.7);
}
.right-bar {
position: absolute;
background-color: transparent;
top: 22px;
left: 16px;
width: 40px;
height: 8px;
display: block;
transform: rotate(-60deg);
float: right;
border-radius: 4px;
}
.right-bar:after {
content: "";
background-color: #F7F2F6;
width: 40px;
height: 8px;
display: block;
float: right;
border-radius: 10px;
z-index: -1;
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.7);
}
.three-bar {
position: absolute;
background-color: transparent;
top: 8px;
left: 8px;
width: 40px;
height: 8px;
display: block;
float: right;
border-radius: 4px;
}
.three-bar:after {
content: "";
background-color: #F7F2F6;
width: 40px;
height: 8px;
display: block;
float: right;
border-radius: 10px;
z-index: -1;
box-shadow: 2px -2px 2px rgba(0, 0, 0, 0.7);
}
.js
this.cameraStatus = {
open: false,
readly: false,
mediaStreamTrack: ""
}
const _this = this;
this.navigator_onit = () =>{
// 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
let copy_mediaDevices= navigator.mediaDevices || {};
if (!navigator.mediaDevices) {
// navigator.mediaDevices = {};
copy_mediaDevices = {}
} else if (copy_mediaDevices.getUserMedia) {
// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
// 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
navigator.mediaDevices.getUserMedia = function (constraints) {
// 首先,如果有getUserMedia的话,就获得它
let copy_navigator= navigator;
let getUserMedia= copy_navigator.webkitGetUserMedia || copy_navigator.mozGetUserMedia || undefined;
// 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
} else {
// 否则,为老的navigator.getUserMedia方法包裹一个Promise
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
}
}
}
this.handleVideo = (val) => {//打开/关闭摄像头
this.cameraStatus.open = val;
if (val == false) {
//关闭摄像头;、
this.cameraStatus.readly = false;
// console.log(this.cameraStatus.mediaStreamTrack)
// console.log(this.cameraStatus.mediaStreamTrack.getTracks())
this.cameraStatus.mediaStreamTrack.getTracks()[0] && this.cameraStatus.mediaStreamTrack.getTracks()[0].stop();
this.cameraStatus.mediaStreamTrack.getTracks()[1] && this.cameraStatus.mediaStreamTrack.getTracks()[1].stop();
} else if (val == true) {
//打开摄像头;
navigator.mediaDevices.getUserMedia({ audio: false, video: true })
.then(function (stream) {
//类型查看
//https://developer.mozilla.org/en-US/docs/Web/API/MediaStream
let video = document.getElementById('camera_video');
// 旧的浏览器可能没有srcObject
if ("srcObject" in video) {
video.srcObject = stream;
_this.cameraStatus.mediaStreamTrack = stream;
} else {
// 防止在新的浏览器里使用它,应为它已经不再支持了
video.src = window.URL.createObjectURL(stream);
_this.cameraStatus.mediaStreamTrack = stream;
}
video.onloadedmetadata = function (e) {
video.play();
_this.cameraStatus.readly = true
};
})
.catch(function (err) {
console.log(err.name + ": " + err.message);
_this.cameraStatus.readly = false
});
}
}
this.handleCapture = () => {//拍照
if (this.cameraStatus.readly) {
let video = document.getElementById('camera_video');
let canvasDOM = document.createElement("canvas");
canvasDOM.width = video.videoWidth;
canvasDOM.height = video.videoHeight;
canvasDOM.getContext('2d').drawImage(video, 0, 0);
let data = canvasDOM.toDataURL('image/webp');
document.getElementById('camera_show').setAttribute('src', data);
//关闭摄像头
this.handleVideo(false)
} else {
alert("相机没有准备好!")
}
}
this.navigator_onit();//对navigator对象进行兼容性初始化处理
let openCapture = document.getElementById("openCapture");
let capture_cancelbtn = document.getElementById("capture_cancelbtn");
let capture = document.getElementById("capture");
openCapture.onclick=()=>{this.handleVideo(true);}
capture_cancelbtn.onclick=()=>{this.handleVideo(false);}
capture.onclick=()=>{this.handleCapture(false);}
在线版效果图:
angular8版代码
democamera.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-democamera',
templateUrl: './democamera.component.html',
styleUrls: ['./democamera.component.scss']
})
export class DemocameraComponent implements OnInit {
exist: {
window: boolean,
navigator: boolean,
mediaDevices: boolean,
getUserMedia: boolean
} = {
window: false,
navigator: false,
mediaDevices: false,
getUserMedia: false
}
cameraStatus: {
open: boolean,
readly: boolean,
mediaStreamTrack: MediaStream | any;
} = {
open: false,
readly: false,
mediaStreamTrack: ""
}
constructor() {
console.log("constructor阶段--我第一个执行1")
this.exist = {
window: window != undefined,
navigator: navigator != undefined,
mediaDevices: navigator.mediaDevices != undefined,
getUserMedia: navigator.mediaDevices.getUserMedia != undefined,
}
}
ngOnInit() {
console.log("ngOnInit阶段--我第二个执行2");
}
ngAfterViewInit() {
console.log("ngAfterViewInit阶段--我第三个执行3");
// 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
let copy_mediaDevices: any = navigator.mediaDevices || {};
if (!navigator.mediaDevices) {
// navigator.mediaDevices = {};
copy_mediaDevices = {}
} else if (copy_mediaDevices.getUserMedia) {
// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
// 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
navigator.mediaDevices.getUserMedia = function (constraints) {
// 首先,如果有getUserMedia的话,就获得它
let copy_navigator: any = navigator;
let getUserMedia: any = copy_navigator.webkitGetUserMedia || copy_navigator.mozGetUserMedia || undefined;
// 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
} else {
// 否则,为老的navigator.getUserMedia方法包裹一个Promise
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
}
}
}
handleVideo = (val) => {
this.cameraStatus.open = val;
const _this = this;
if (val == false) {
//关闭摄像头;
console.log(this.cameraStatus.mediaStreamTrack)
console.log(this.cameraStatus.mediaStreamTrack.getTracks())
if (this.cameraStatus.mediaStreamTrack.getTracks) {
this.cameraStatus.mediaStreamTrack.getTracks()[0] && this.cameraStatus.mediaStreamTrack.getTracks()[0].stop();
this.cameraStatus.mediaStreamTrack.getTracks()[1] && this.cameraStatus.mediaStreamTrack.getTracks()[1].stop();
}
} else if (val == true) {
//打开摄像头;
navigator.mediaDevices.getUserMedia({ audio: false, video: true })
.then(function (stream) {
//类型查看
//https://developer.mozilla.org/en-US/docs/Web/API/MediaStream
let video: HTMLAudioElement | any = document.getElementById('camera_video');
// 旧的浏览器可能没有srcObject
if ("srcObject" in video) {
video.srcObject = stream;
_this.cameraStatus.mediaStreamTrack = stream;
} else {
// 防止在新的浏览器里使用它,应为它已经不再支持了
video.src = window.URL.createObjectURL(stream);
_this.cameraStatus.mediaStreamTrack = stream;
}
video.onloadedmetadata = function (e) {
video.play();
_this.cameraStatus.readly = true
};
})
.catch(function (err) {
console.log(err.name + ": " + err.message);
_this.cameraStatus.readly = false
});
}
}
handleCapture = () => {
if (this.cameraStatus.readly) {
let video: HTMLAudioElement | any = document.getElementById('camera_video');
let canvasDOM: HTMLCanvasElement = document.createElement("canvas");
canvasDOM.width = video.videoWidth;
canvasDOM.height = video.videoHeight;
canvasDOM.getContext('2d').drawImage(video, 0, 0);
let data = canvasDOM.toDataURL('image/webp');
document.getElementById('camera_show').setAttribute('src', data);
//关闭摄像头
this.handleVideo(false)
} else {
alert("相机没有准备好!")
}
}
}
democamera.component.html
<ul>
<li>window是否存在?{{exist.window}}</li>
<li>navigator是否存在?{{exist.navigator}}</li>
<li>navigator.mediaDevices是否存在?{{exist.mediaDevices}}</li>
<li>navigator.mediaDevices.getUserMedia是否存在?{{exist.getUserMedia}}</li>
</ul>
<button (click)="handleVideo(true)" *ngIf="cameraStatus.open===false">打开摄像头</button>
<div class="capture_box" style="position: relative;
width: 640px;
height: 480px;
display: flex;
justify-content: center;" *ngIf="cameraStatus.open===true">
<div class="capture_buttonsbox">
<div class="capture_cancel">
<a class="arrow-icon" (click)="handleVideo(false)">
<span class="left-bar"></span>
<span class="right-bar"></span>
<span class="three-bar"></span>
</a>
</div>
<div class="capture_confirm1" (click)="handleCapture()" id="capture">
<div class="capture_confirm2"></div>
</div>
</div>
<video id="camera_video" style="width: 100%;
height: 100%;
border: 2px blue solid;
z-index: 1000;"></video>
</div>
<br />
<img src="" id="camera_show" alt="暂无照片" title="photo">
democamera.component.scss
.capture_buttonsbox {
height: 80px;
width: 100%;
position: absolute;
bottom: 16px;
z-index: 1001;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.capture_confirm1 {
background: linear-gradient(#F7F2F6, #B2AC9E);
box-shadow: 0 0 8px rgba(0, 0, 0, 0.3), 0 5px 5px rgba(0, 0, 0, 0.5);
border-radius: 24px;
height: 50px;
width: 50px;
display: inline-flex;
justify-content: center;
align-items: center;
position: absolute;
bottom: 16px;
z-index: 1001;
.capture_confirm2 {
height: 30px;
width: 30px;
border-radius: 24px;
background: linear-gradient(#CBC7BC, #D2CBC3);
}
&:hover {
cursor: pointer;
}
&:active {
box-shadow: 0 0 8px rgba(0, 0, 0, 0.3), 0 5px 5px rgba(0, 0, 0, 0.2);
}
}
.capture_cancel {
// background-color: green;
width: 50%;
align-self: flex-start;
display: inline-flex;
justify-content: center;
align-items: center;
left: 0;
.arrow-icon {
/* background-color: lightcoral; */
background-color: transparent;
height: 52px;
width: 58px;
position: relative;
cursor: pointer;
border-radius: 4px;
transform-origin: center center;
transform: rotate(90deg);
clear: both;
.left-bar {
position: absolute;
background-color: transparent;
top: 22px;
left: 0;
width: 40px;
height: 8px;
display: block;
transform: rotate(60deg);
float: right;
border-radius: 4px;
&:after {
content: "";
background-color: #F7F2F6;
width: 40px;
height: 8px;
display: block;
float: right;
border-radius: 10px;
z-index: -1;
box-shadow: 0px -2px 2px rgba(0, 0, 0, 0.7);
}
}
.right-bar {
position: absolute;
background-color: transparent;
top: 22px;
left: 16px;
width: 40px;
height: 8px;
display: block;
transform: rotate(-60deg);
float: right;
border-radius: 4px;
&:after {
content: "";
background-color: #F7F2F6;
width: 40px;
height: 8px;
display: block;
float: right;
border-radius: 10px;
z-index: -1;
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.7);
}
}
.three-bar {
position: absolute;
background-color: transparent;
top: 8px;
left: 8px;
width: 40px;
height: 8px;
display: block;
float: right;
border-radius: 4px;
&:after {
content: "";
background-color: #F7F2F6;
width: 40px;
height: 8px;
display: block;
float: right;
border-radius: 10px;
z-index: -1;
box-shadow: 2px -2px 2px rgba(0, 0, 0, 0.7);
}
}
}
}
}
angular8版效果图
======题外话
因为得自己设计拍照页面,所以用css画了两个按钮,需要的自提。
效果图: