飞雪连天射白鹿,笑书神侠倚碧鸳

0%

用Web Components写摄像头组件

Web Components中最佳特性就是shadow DOM的防污染封装
来看看怎么实现一个摄像头组件吧~

原文:大漠w3c


  • 使用MediaDevices API获取相机访问权限
  • 使用video元素播放MediaStream
  • 使用canvas元素以blob或base64形式拍照
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// <simple-camera></simple-camera>
// <button id="btnPhoto">拍一张</button>
// <button id="btnBase64Photo">转为base64</button>
// <img id="base64Img" src="#" alt="">

class SimpleCamera extends HTMLElement {
constructor() {
super();
} // 当元素连接到DOM时调用这个函数
connectedCallback() {
// shadow样式保持
const shadow = this.attachShadow({
mode: 'open'
});

this.videoElement = document.createElement('video');
this.canvasElement = document.createElement('canvas');
this.videoElement.setAttribute('playsinline', true);
this.canvasElement.style.display = 'none';

shadow.appendChild(this.videoElement);
shadow.appendChild(this.canvasElement)
}

// 分配给目标元素执行
open(constraints) {
// 本地测试不要使用ip,要用localhost,否则没有navigator.mediaDevices
return navigator.mediaDevices.getUserMedia(constraints)
.then((mediaStream) => {
// 分配MediaStream
this.videoElement.srcObject = mediaStream;
// 加载时播放流
this.videoElement.onloadedmetadata = (e) => {
this.videoElement.play()
}
})
}

// 从video元素中绘制帧
_drawImage() {
const imageWidth = this.videoElement.videoWidth;
const imageHeight = this.videoElement.videoHeight;
this.canvasElement.width = imageWidth;
this.canvasElement.height = imageHeight;

const context = this.canvasElement.getContext('2d');
context.drawImage(this.videoElement, 0, 0, imageWidth, imageHeight);
return {
imageHeight,
imageWidth
}
}

// canvas的toBlob()转为同步
takeBlobPhoto() {
const {
imageHeight,
imageWidth
} = this._drawImage();
return new Promise((resolve, reject) => {
this.canvasElement.toBlob((blob) => {
resolve({
blob,
imageHeight,
imageWidth
})
})
})
}

// canvase转base64
takeBase64Photo({
type,
quality
} = {
type: 'png',
quality: 1
}) {
const {
imageHeight,
imageWidth
} = this._drawImage();
const base64 = this.canvasElement.toDataURL('image/' + type, quality);
return {
base64,
imageHeight,
imageWidth
}
}

}
// 可以使用自定义元素了
customElements.define('simple-camera', SimpleCamera)

// 业务逻辑
(async function() {
const camera = document.querySelector('simple-camera')
const btnBlobPhoto = document.querySelector('#btnBlobPhoto')
const btnBase64Photo = document.querySelector('#btnBase64Photo')
const base64Img = document.getElementById('base64Img');

await camera.open({
video: {
facingMode: 'user'
}
}).catch(err => {
console.log('摄像头开启失败', err)
})

btnPhoto.addEventListener('click', async event => {
const photo = await camera.takeBlobPhoto()
console.log(photo)
base64Img.src = URL.createObjectURL(photo.blob);
})

btnBase64Photo.addEventListener('click', async event => {
const photo = camera.takeBase64Photo({
type: 'jpeg',
quality: 1
})
console.log(photo)
base64Img.src = photo.base64;
})
}())
听说,打赏我的人最后都找到了真爱
↘ 此处应有打赏 ↙
// 用户脚本