您现在的位置是:首页 > 诗句大全

前端调取摄像头并实现拍照功能

作者:言安琪时间:2024-04-27 18:50:16分类:诗句大全

简介  文章浏览阅读855次。实现拍照的整体思路其实很简单,仅仅需要了解到视频其实也是一帧一帧画面构成的,而canvas恰好有捕捉当前帧的能力。预告:在下一篇会讲解如何实现扫一扫的功能,需要用到插件,感兴趣的同学可以先预习一下功课。

点击全文阅读

前言: 最近接到的一个需求十分有意思,设计整体实现了前端仿微信扫一扫的功能。整理了一下思路,做一个分享。

tips: 如果想要实现完整扫一扫的功能,你需要掌握一些前置知识,这次我们先讲如何实现拍照并且保存的功能。

一. window.navigator

你想调取手机的摄像头,首先你得先检验当前设备是否有摄像设备,window 身上自带了一个 navigator 属性,这个对象有一个叫做 mediaDevices 的属性是我们即将用到的。

于是我们就可以先设计一个叫做 checkCamera 的函数,用来在页面刚开始加载的时候执行。。
image.png

我们先看一下这个对象有哪些方法,你也许会看到下面的场景,会发现这个属性身上只有一个值为 nullondevicechange 属性,不要怕,真正要用的方法其实在它的原型身上。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们点开它的原型属性,注意下面这两个方法,这是我们本章节的主角。
image.png

我们到这一步只是需要判断当前设备是否有摄像头,我们先调取 enumerateDevices 函数来查看当前媒体设备是否存在。它的返回值是一个 promise 类型,我们直接用 asyncawait 来简化一下代码。
image.png
image.png
从上图可以看出,我的电脑有两个音频设备和一个视频设备,那么我们就可以放下进行下一步了。

二. 获取摄像头

接下来就需要用到上面提到的第二个函数,navigator.getUserMedia。这个函数接收一个对象作为参数,这个对象可以预设一些值,来作为我们请求摄像头的一些参数。

这里我们的重点是 facingMode 这个属性,因为我们扫一扫一般都是后置摄像头
image.png
当你执行了这个函数以后,你会看到浏览器有如下提示:
image.png

于是你高兴的点击了允许,却发现页面没有任何变化。
image.png

这里你需要知道,这个函数只是返回了一个媒体流信息给你,你可以这样简单理解刚刚我们干了什么,首先浏览器向手机申请我想用一下摄像头可以吗?在得到了你本人的确认以后,手机将摄像头的数据线递给了浏览器,:“诺,给你。”

浏览器现在仅仅拿到了一根数据线,然而浏览器不知道需要将这个摄像头渲染到哪里,它不可能自动帮你接上这根线,你需要自己找地方接上这根数据线。

这里不卖关子,我们需要请到我们的 Video 标签。我没听错吧?那个播放视频的 video 标签?没错,就是原生的 video 标签。

这里创建一个 video 标签,然后打上 ref 来获取这个元素。
image.png

这里的关键点在于将流数据赋值给 video 标签的 srcObject 属性。就好像你拿到了数据线,插到了显示器上。
(tips: 这里需要特别注意,不是 video.src 而是 video.srcObject 请务必注意)
image.png

现在你应该会看到摄像头已经在屏幕上展示了,这里是我用电脑前置摄像头录制的一段视频做成了gif。(脉动请给我打钱,哼)
camera.gif

三. 截取当前画面

这里我随手写了一个按钮当作拍摄键,接下来我们将实现点击这个按钮截取当前画面。
image.png

这里你需要知道一个前提,虽然我们现在看到的视频是连贯的,但其实在浏览器渲染的时候,它其实是一帧一帧渲染的。就像宫崎骏有些动漫一样,是一张一张手写画。

让我们打开 Performance 标签卡,记录一下打开掘金首页的过程,可以看到浏览器的整个渲染过程其实也是一帧一帧拼接到一起,才完成了整个页面的渲染。
11.gif

知道了这个前提,那么举一反三,我们就可以明白,虽然我们现在已经打开了摄像头,看到的视频好像是在连贯展示,但其实它也是一帧一帧拼到一起的。那现在我们要做的事情就非常明了,当我按下按钮的时候,想办法将 video 标签当前的画面保存下来。

这里不是特别容易想到,我就直接说答案了,在这个场景,我们需要用到 canvas 的一些能力。不要害怕,我目前对 canvas 的使用也不是特别熟练,今天也不会用到特别复杂的功能。

首先创建一个空白的 canvas 元素,元素的宽高设置为和 video 标签一致。
image.png

接下来是重点: 我们需要用到 canvasgetContext 方法,先别着急头晕,这里你只需要知道,它接受一个字符串 "2d" 作为参数就行了,它会把这个画布的上下文返回给你。( tips 如果这里还不清楚上下文的概念,也不用担心,这里你就简单理解为把这个 canvas 这个元素加工了一下,帮你在它身上添加了一些新的方法而已。)
image.png

在这个 ctx 对象身上,我们只需要用到一个 drawImage 方法即可,不需要关心其它属性。
image.png

感觉参数有点多?没关系,我们再精简一下,我们只需要考虑第二个用法,也就是5参数的写法。(sx,sy 是做裁切用到的,本文用不到,感兴趣可以自行了解。)
image.png

这里先简单解释一下 dxdy 是什么意思。在 canvas 里也存在一个看不见的坐标系,起点也是左上角。设想你想在一个 HTMLbody 元素里写一个距离左边距离 100px 距离顶部 100px的画面,是不是得写 margin-left:100px margin-top:100px 这样的代码?没错,这里的 dydx 也是同样的道理。
image.png

我们再看 dwidth,和 dheight,从这个名字你就能才出来,肯定和我们将要在画笔里画画的元素的宽度和高度有关,是的,你猜的没错,它就好像你设置一个 div 元素的高度和宽度一样,代表着你将在画布上画的截图的宽高属性。

现在只剩下第一个参数还没解释,这里直接说答案,我们可以直接将 video 标签填进去,ctx 会自动将当前 video 标签的这一帧画面填写进去。现在按钮的代码应该是这个样子。

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);//将 canvas 投到页面上}

测试一下效果。
112.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 恰好有捕捉当前帧的能力。

预告:在下一篇会讲解如何实现扫一扫的功能,需要用到插件,感兴趣的同学可以先预习一下功课。🎁二维码扫码插件

趁热打铁🧭:前端实现微信扫一扫的思路

点击全文阅读

郑重声明:

本站所有活动均为互联网所得,如有侵权请联系本站删除处理

我来说两句