前端调取摄像头并实现拍照功能
前言: 最近接到的一个需求十分有意思,设计整体实现了前端仿微信扫一扫的功能。整理了一下思路,做一个分享。
tips: 如果想要实现完整扫一扫的功能,你需要掌握一些前置知识,这次我们先讲如何实现拍照并且保存的功能。
一. window.navigator
-
你想调取手机的摄像头,首先你得先检验当前设备是否有摄像设备,window 身上自带了一个 navigator 属性,这个对象有一个叫做 mediaDevices 的属性是我们即将用到的。
-
于是我们就可以先设计一个叫做
checkCamera
的函数,用来在页面刚开始加载的时候执行。。 -
我们先看一下这个对象有哪些方法,你也许会看到下面的场景,会发现这个属性身上只有一个值为 null 的 ondevicechange 属性,不要怕,真正要用的方法其实在它的原型身上。
-
让我们点开它的原型属性,注意下面这两个方法,这是我们本章节的主角。
-
我们到这一步只是需要判断当前设备是否有摄像头,我们先调取 enumerateDevices 函数来查看当前媒体设备是否存在。它的返回值是一个 promise 类型,我们直接用 async 和 await 来简化一下代码。 从上图可以看出,我的电脑有两个音频设备和一个视频设备,那么我们就可以放下进行下一步了。
二. 获取摄像头
-
接下来就需要用到上面提到的第二个函数,navigator.getUserMedia。这个函数接收一个对象作为参数,这个对象可以预设一些值,来作为我们请求摄像头的一些参数。
-
这里我们的重点是 facingMode 这个属性,因为我们扫一扫一般都是后置摄像头 当你执行了这个函数以后,你会看到浏览器有如下提示:
-
于是你高兴的点击了允许,却发现页面没有任何变化。
-
这里你需要知道,这个函数只是返回了一个媒体流信息给你,你可以这样简单理解刚刚我们干了什么,首先浏览器向手机申请我想用一下摄像头可以吗?在得到了你本人的确认以后,手机将摄像头的数据线递给了浏览器,:“诺,给你。”
-
但浏览器现在仅仅拿到了一根数据线,然而浏览器不知道需要将这个摄像头渲染到哪里,它不可能自动帮你接上这根线,你需要自己找地方接上这根数据线。
-
这里不卖关子,我们需要请到我们的 Video 标签。我没听错吧?那个播放视频的
video
标签?没错,就是原生的video
标签。 -
这里创建一个 video 标签,然后打上 ref 来获取这个元素。
-
这里的关键点在于将流数据赋值给 video 标签的 srcObject 属性。就好像你拿到了数据线,插到了显示器上。 (tips: 这里需要特别注意,不是 video.src 而是 video.srcObject 请务必注意)
-
现在你应该会看到摄像头已经在屏幕上展示了,这里是我用电脑前置摄像头录制的一段视频做成了gif。(脉动请给我打钱,哼)
三. 截取当前画面
四. 源码
<script lang="ts" setup>
import { ref, onMounted } from "vue";
const wrapper = ref<HTMLDivElement>();
const videoEl = ref<HTMLVideoElement>();
async function checkCamera() {
const navigator = window.navigator.mediaDevices;
const devices = await navigator.enumerateDevices();
if (devices) {
const stream = await navigator.getUserMedia({
audio: false,
video: {
width: 300,
height: 300,
// facingMode: { exact: "environment" }, //强制后置摄像头
facingMode: "user", //前置摄像头
},
});
if (!videoEl.value) return;
videoEl.value.srcObject = stream;
videoEl.value.play();
}
}
function shoot() {
if (!videoEl.value || !wrapper.value) return;
const canvas = document.createElement("canvas");
canvas.width = videoEl.value.videoWidth;
canvas.height = videoEl.value.videoHeight;
//拿到 canvas 上下文对象
const ctx = canvas.getContext("2d");
ctx?.drawImage(videoEl.value, 0, 0, canvas.width, canvas.height);
wrapper.value.appendChild(canvas);
}
onMounted(() => {
checkCamera();
});
</script>
<template>
<div ref="wrapper" class="w-full h-full bg-red flex flex-col items-center">
<video ref="videoEl" />
<div
@click="shoot"
class="w-100px leading-100px text-center bg-black text-30px"
>
拍摄
</div>
</div>
</template>
五. 总结
实现拍照的整体思路其实很简单,仅仅需要了解到视频其实也是一帧一帧画面构成的,而 canvas 恰好有捕捉当前帧的能力。
预告:在下一篇会讲解如何实现扫一扫的功能,需要用到插件,感兴趣的同学可以先预习一下功课。🎁二维码扫码插件