前言
哈喽,大家好,我是海怪。
最近一直在看 WebRTC 的用法,也学了一下音视频流的东西,今天就跟大家分享一个好玩的小实战吧——给自己拍照。
项目已上传至 Github,Repo 地址:https://github.com/haixiangyan/webrtc-take-photo[1]
页面结构
首先,我们要拆分一下实现步骤:
- 打开摄像头,获取视频流
- 需要一个
<video>
来播放摄像头的画面 - 点击按钮,生成画面,并展示在
<img>
里
因此,我们需要 <video>
, <img>
以及 <button>
。
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main>
<video id="video">浏览器不支持 Video</video>
<canvas id="canvas">
<img id="photo" alt="拍照后的照片">
</canvas>
</main>
<div class="actions">
<button id="takePhotoButton" type="button" class="btn btn-primary">拍照</button>
<button id="downloadButton" type="button" class="btn btn-success">下载</button>
<button id="clearButton" type="button" class="btn btn-danger">清空</button>
</div>
<script src="./main.js"></script>
</body>
再加上点 CSS,让整个 App 好看一点~
代码语言:javascript复制main {
padding: 24px 24px 16px;
display: flex;
}
video {
margin-right: 16px;
box-sizing: content-box;
border: 4px solid #ffaabb;
}
canvas {
box-sizing: content-box;
border: 4px solid #aabbff;
}
.actions {
padding: 0 24px;
}
.actions button {
margin-right: 16px;
}
左边为摄像头的 <video>
,右边则是拍照的图片。
打开摄像头
打开摄像头这一步,其实是调用了 WebRTC 的一个重要接口 navigator.mediaDevices.getUserMedia
,通过这个接口不仅可以完成用户对摄像头的使用授权,还可以从返回值里直接拿到视频流:
const start = async () => {
video = document.getElementById('video');
canvas = document.getElementById('canvas');
photo = document.getElementById('photo');
takePhotoButton = document.getElementById('takePhotoButton');
downloadButton = document.getElementById('downloadButton');
// 获取摄像头的视频流
try {
video.srcObject = await navigator.mediaDevices.getUserMedia({video: true, audio: false})
video.play()
} catch (e) {
console.error(e)
}
}
start().then()
将视频流接到 <video>
元素的 srcObject
上,再调用一下 video.play()
就可以展示视频流了:
拍照
从 HTML 结构里我们可以看到 <canvas>
里面还藏着一个 <img>
。这里 <canvas>
的作用是负责从视频流中生成图片数据,再将这个数据放到 <img>
的 src
上,这样就完成了我们的拍照功能了。
const takePhoto = () => {
const context = canvas.getContext('2d')
if (width && height) {
// 将 video 元素的 width 和 height 拿过来
canvas.width = width;
canvas.height = height;
context.drawImage(video, 0, 0, width, height);
// 生成图片
const data = canvas.toDataURL('image/png');
photo.setAttribute('src', data);
} else {
clearPhoto()
}
}
不过在调用 drawImage
的时候要传入对应的宽和高,这里的宽和高可以从 <video>
元素中的 width
和 height
获得。
const start = async () => {
video = document.getElementById('video');
canvas = document.getElementById('canvas');
photo = document.getElementById('photo');
takePhotoButton = document.getElementById('takePhotoButton');
downloadButton = document.getElementById('downloadButton');
// 获取摄像头的视频流
try {
video.srcObject = await navigator.mediaDevices.getUserMedia({video: true, audio: false})
video.play()
} catch (e) {
console.error(e)
}
video.addEventListener('canplay', (event) => {
if (!streaming) {
// 按比例放大 videoHeight
height = video.videoHeight / (video.videoWidth / width);
// 设置 video 的宽高
video.setAttribute('width', width);
video.setAttribute('height', height);
// 设置 canvas 的宽高
canvas.setAttribute('width', width);
canvas.setAttribute('height', height);
streaming = true;
}
}, false)
takePhotoButton.addEventListener('click', (event) => {
// 拍照
takePhoto()
}, false)
}
start().then()
这样一来就能生成视频中定格后的某一个画面了。
从上面可以看到这里的 <img>
里的 src
是 data:xxx
的图片数据。
清空图片
如果要清除已经拍好的照片呢?我们可以利用 <canvas>
的 fillRect
来生成一个空白图片,然后再转化成图片数据,放到 src
里就可以了:
// 清空操作
const clearPhoto = () => {
const context = canvas.getContext('2d')
// 生成空白图片
context.fillStyle = "#AAA";
context.fillRect(0, 0, canvas.width, canvas.height);
const data = canvas.toDataURL('image/png');
photo.setAttribute('src', data);
}
// 开始
const start = async () => {
// ...
clearButton.addEventListener('click', (event) => {
clearPhoto();
})
// 生成默认空白图片
clearPhoto();
}
start().then()
或许有的人会觉得:我把图片 v-if=false
、display: none
、visibility: hidden
不也可以嘛?
当然可以!这里我只是想再分享另一种思路嘛~ 因为像这种调用 fillRect
来做重置功能的是比较常用的,比较画板里的重置就可以这样来清空画布。
下载
下载则比较简单了,也是面试常考的一道技巧题。
先生成一个 <a>
标签,然后通过 <canvas>
生成 URL,将这个 URL 放到 href
里,用 JS 出发 click
事件,就可模拟下载了:
// 下载操作
const downloadPhoto = () => {
const link = document.createElement('a');
link.download = '你的帅照.png';
link.href = canvas.toDataURL();
link.click();
}
const start = async () => {
// ...
downloadButton.addEventListener('click', (event) => {
// 下载
downloadPhoto()
})
}
start().then()
总结
到这里,这个小实战就结束啦,并没有什么难度,这里稍微做下总结吧。
WebRTC 最重要的 API 就是 await navigator.mediaDevices.getUserMedia({video: true, audio: false})
,通过返回值可以获取当前摄像头、麦克风的音视频流。
通过 <video>
元素的 srcObject
属性可以直接接上视频流,这在直播、P2P、视频聊天的场景都可以这样使用。
通过 <canvas>
的 drawImage
以及 fillRect
来生成视频图片以及空白图片数据,再把这些数据放到 <img>
就可以实现 JS 生成画面的效果。
如果你也喜欢我的文章,也可以关注我,你的三连是我最大的动力~
参考资料
[1]
项目地址: https://github.com/haixiangyan/webrtc-take-photo