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

news2024/9/22 13:42:58

前言

最近接到的一个需求十分有意思,设计整体实现了前端仿 微信扫一扫 的功能。整理了一下思路,做一个分享。
tips: 如果想要实现完整扫一扫的功能,你需要掌握一些前置知识,这次我们先讲如何实现拍照并且保存的功能。

一. window.navigator

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

  2. 于是我们就可以先设计一个叫做 checkCamera 的函数,用来在页面刚开始加载的时候执行。。
    请添加图片描述

  3. 我们先看一下这个对象有哪些方法,你也许会看到下面的场景,会发现这个属性身上只有一个值为 null 的 ondevicechange 属性,不要怕,真正要用的方法其实在它的原型身上。
    请添加图片描述

  4. 让我们点开它的原型属性,注意下面这两个方法,这是我们本章节的主角。
    请添加图片描述

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

二. 获取摄像头

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

  2. 这里我们的重点是 facingMode 这个属性,因为我们扫一扫一般都是后置摄像头
    请添加图片描述
    当你执行了这个函数以后,你会看到浏览器有如下提示:
    请添加图片描述
    于是你高兴的点击了允许,却发现页面没有任何变化。
    这里你需要知道,这个函数只是返回了一个媒体流信息给你,你可以这样简单理解刚刚我们干了什么,首先浏览器向手机申请我想用一下摄像头可以吗?在得到了你本人的确认以后,手机将摄像头的数据线递给了浏览器,:“诺,给你。”

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

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

  1. 这里创建一个 video 标签,然后打上 ref 来获取这个元素。
    请添加图片描述
  2. 这里的关键点在于将流数据赋值给 video 标签的 srcObject 属性。就好像你拿到了数据线,插到了显示器上。
    (tips: 这里需要特别注意,不是 video.src 而是 video.srcObject 请务必注意)
    请添加图片描述
  3. 现在你应该会看到摄像头已经在屏幕上展示了。

三. 截取当前画面

当我按下按钮的时候,想办法将 video 标签当前的画面保存下来。在这个场景,我们需要用到 canvas 的一些能力。不要害怕,我目前对 canvas 的使用也不是特别熟练,今天也不会用到特别复杂的功能。

  1. 首先创建一个空白的 canvas 元素,元素的宽高设置为和 video 标签一致。
    请添加图片描述

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

  3. 在这个 ctx 对象身上,我们只需要用到一个 drawImage 方法即可,不需要关心其它属性。
    请添加图片描述

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

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

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

  7. 现在只剩下第一个参数还没解释,这里直接说答案,我们可以直接将 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 投到页面上
}

四. 源码


<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 恰好有捕捉当前帧的能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1237786.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis篇---第十四篇

系列文章目录 文章目录 系列文章目录前言一、为什么Redis的操作是原子性的,怎么保证原子性的?二、了解Redis的事务吗?四、Redis 的数据类型及使用场景前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男…

windows11记事本应用程序无法打开,未响应,崩溃,卡死

windows11记事本应用程序无法打开&#xff0c;未响应&#xff0c;崩溃&#xff0c;卡死 文章目录 问题描述搜索引擎&#xff08;度娘&#xff09;卸载后如何安装问题未解决另一个解决方案&#xff1a;步骤&#xff1a;1.设置 → 语音和区域 → 输入2.选择“高级键盘设置”3.替…

计数排序java实现

计数排序假设n个输入元素中的每一个都是在0到k区间的一个整数&#xff0c;其中k为某个整数&#xff0c;当kO(n)时&#xff0c;排序的运行时间为θ(n)。 计数排序的基本思想是&#xff1a;对每一个输入元素x&#xff0c;确定小于x的元素个数。利用这一信息&#xff0c;就可以直…

LeetCode:2304. 网格中的最小路径代价(C++)

目录 2304. 网格中的最小路径代价 题目描述&#xff1a; 实现代码&#xff1a; dp&#xff08;dp有很多相似的经典题目&#xff0c;比较简单&#xff0c;不再给出解析&#xff09; 2304. 网格中的最小路径代价 题目描述&#xff1a; 给你一个下标从 0 开始的整数矩阵 grid …

神经网络常用激活函数详解

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

C++:AVL树(平衡二叉树)

引言&#xff1a; AVL树是一种特殊的二叉搜索树&#xff0c;二叉搜索树虽然可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找元素相当于在顺序表中搜索元素&#xff0c;效率低下。因此&#xff0c;两位俄罗斯的数学家G.M.Ade…

揭秘成都瀚网科技有限公司:抖音带货是否靠谱?

随着抖音等短视频平台的兴起&#xff0c;越来越多的商家和网红开始利用这些平台进行带货销售。这其中&#xff0c;成都瀚网科技有限公司&#xff08;以下简称瀚网科技&#xff09;也宣称能够在抖音等平台上实现带货销售&#xff0c;那么这家公司是否靠谱呢&#xff1f;本文将为…

外贸自建站的指南?新手如何玩转海洋建站?

外贸自建站工具有哪些&#xff1f;外贸新手怎么搭建独立网站&#xff1f; 拥有自己的外贸网站是提高企业国际竞争力和扩大市场份额的有效途径。然而&#xff0c;许多企业在外贸自建站的过程中感到困惑。海洋建站将为您提供一份详细的外贸自建站指南&#xff0c;助您轻松打造一…

数据库实验二 数据库表的数据插入、修改、删除操作

数据库实验二 数据库表的数据插入、修改、删除操作 一、实验目的二、设计性实验三、观察与思考 一、实验目的 1&#xff0e;掌握MySQL数据库表的数据插入、修改、删除操作SQL语法格式 2&#xff0e;掌握数据表的数据的录入、增加和删除的方法 二、设计性实验 某超市的食品管…

Oracle(2-5)Usage and Configuration of the Oracle Shared Server

文章目录 一、基础知识1、 Server Configurations服务器配置2、Dedicated server process专用服务器进程3、Oracle Shared ServerOracle共享服务器4、Benefits of Shared Server 共享服务器的优点5、Processing a Request 处理请求6、Configuring Shared Server 配置共享服务器…

计算公式-dB转换,噪声,IP3,OP1dB,耗散

1. dB和log转换公式 dB在缺省情况下总是定义功率单位&#xff0c;以 10lg 为计。 d B 10 l g ( B ) dB 10lg(B) dB10lg(B) P o w e r G a i n ( d B ) 10 l g ( P o u t P i n ) Power Gain(dB) 10lg(\frac{P_{out}}{P_{in}}) PowerGain(dB)10lg(Pin​Pout​​) 2. 级联情…

爬取春秋航空航班信息

一、使用fiddler爬取小程序春秋航空航班信息 使用Fiddler爬取春秋航空微信小程序&#xff08;手机上由于网络问题&#xff0c;无法进入&#xff0c;使用电脑版&#xff09; 搜索航班信息 搜索记录 使用Fiddler查找url(没有得到有效url) 继续查找&#xff0c;发现航班信息列…

uniapp 富文本以及移动端富文本的展示问题

富文本展示有几种方式: 1.<view v-html"content"></view> 2. uniapp自带组件 rich-text rich-text | uni-app官网 <rich-text :nodes"content"></rich-text> 3.uView组件 u-parse Parse 富文本解析器 | uView 2.0 - 全面兼…

Docker安装Rabbitmq3.12并且prometheus进行监听【亲测可用】

一、安装Rabbitmq 下载镜像&#xff1a; docker pull rabbitmq:3.12-management 安装镜像&#xff1a; docker run -id --restartalways --namerabbitmq -v /usr/local/rabbitmq:/var/lib/rabbitmq -p 15692:15692 -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USERgu…

普冉PY32系列(十一) 基于PY32F002A的6+1通道遥控小车II - 控制篇

目录 普冉PY32系列(一) PY32F0系列32位Cortex M0 MCU简介普冉PY32系列(二) Ubuntu GCC Toolchain和VSCode开发环境普冉PY32系列(三) PY32F002A资源实测 - 这个型号不简单普冉PY32系列(四) PY32F002A/003/030的时钟设置普冉PY32系列(五) 使用JLink RTT代替串口输出日志普冉PY32…

电脑磁盘怎么设置密码?磁盘加密软件哪个好?

电脑磁盘经常被用于存放各种重要数据&#xff0c;而为了避免数据泄露&#xff0c;我们需要为磁盘设置密码&#xff0c;以防止未授权人员使用磁盘。那么&#xff0c;电脑磁盘怎么设置密码呢&#xff1f;下面我们就一起来了解一下。 如何设置磁盘密码&#xff1f; 想要为磁盘设置…

腾讯云服务器99元一年?假的,阿里云是99元

腾讯云服务器99元一年是真的吗&#xff1f;假的&#xff0c;不用99元&#xff0c;只要88元即可购买一台2核2G3M带宽的轻量应用服务器&#xff0c;99元太多了&#xff0c;88元就够了&#xff0c;腾讯云百科活动 txybk.com/go/txy 活动打开如下图&#xff1a; 腾讯云服务器价格 腾…

Redis主从复制,哨兵和Cluster集群

主从复制&#xff1a; 主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份&#xff08;和同步&#xff09;&#xff0c;以及对于读操作的负载均衡和简单的故障恢复。 缺陷&#xff1a;故障恢复无法自动化…

Java核心知识点整理大全8-笔记

Java核心知识点整理大全7-笔记-CSDN博客文章浏览阅读1.2k次&#xff0c;点赞27次&#xff0c;收藏26次。但是如果锁的竞争激烈&#xff0c;或者持有锁的线程需要长时间占用锁执行同步块&#xff0c;这时候就不适合 使用自旋锁了&#xff0c;因为自旋锁在获取锁前一直都是占用 c…

什么是凸函数

假设函数是定义在某个向量空间的凸子集上的实值函数&#xff0c;并且&#xff0c;如果对于中的任何两个向量和&#xff0c;都满足&#xff1a; 则称为上的凸函数