当前位置: 首页 > news >正文

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

前言

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

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

一. window.navigator

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

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

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

在这里插入图片描述

  1. 让我们点开它的原型属性,注意下面这两个方法,这是我们本章节的主角。
    在这里插入图片描述

  2. 我们到这一步只是需要判断当前设备是否有摄像头,我们先调取 enumerateDevices 函数来查看当前媒体设备是否存在。它的返回值是一个 promise 类型,我们直接用 asyncawait 来简化一下代码。
    在这里插入图片描述

在这里插入图片描述

从上图可以看出,我的电脑有两个音频设备和一个视频设备,那么我们就可以放下进行下一步了。 

二. 获取摄像头

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

  2. 这里我们的重点是 facingMode 这个属性,因为我们扫一扫一般都是后置摄像头
    在这里插入图片描述

  3. 当你执行了这个函数以后,你会看到浏览器有如下提示:

在这里插入图片描述

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

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

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

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

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

在这里插入图片描述

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

在这里插入图片描述

  1. 现在你应该会看到摄像头已经在屏幕上展示了

三. 截取当前画面

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

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

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

在这里插入图片描述

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

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

  3. 首先创建一个空白的 canvas 元素,元素的宽高设置为和 video 标签一致。
    在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

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

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

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

```js
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>

相关文章:

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

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

android —— 阴影效果和跑马灯效果Textview

1、带阴影的TextView ①、 android:shadowColor“color/black” 设置阴影颜色&#xff0c;需要与shadowRadius一起使用 ②、android:shadowRadius“3.0” 设置阴影模糊程度&#xff0c;设为0.1会变成字体颜色&#xff0c;建议设置3.0 ③、android:shadowDx“10” 设置阴影在水…...

多态语法详解

多态语法详解 一&#xff1a;概念1&#xff1a;多态实现条件 二:重写&#xff1a;三&#xff1a;向上转型和向下转型1:向上转型&#xff1a;1&#xff1a;直接赋值&#xff1a;2&#xff1a;方法传参3&#xff1a;返回值 2:向下转型 一&#xff1a;概念 1&#xff1a;同一个引…...

Python大数据之linux学习总结——day11_ZooKeeper

ZooKeeper ZK概述 ZooKeeper概念: Zookeeper是一个分布式协调服务的开源框架。本质上是一个分布式的小文件存储系统 ZooKeeper作用: 主要用来解决分布式集群中应用系统的一致性问题。 ZooKeeper结构: 采用树形层次结构&#xff0c;ZooKeeper树中的每个节点被称为—Znode。且树…...

C语言——函数的嵌套调用

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h>void new_line() {printf("Hello\n"); }void three_line() {int i0;for(i0;i<3;i){new_line();} }int main() {three_line();return 0; }...

4种经典的限流算法与集群限流

0、基础知识 1000毫秒内&#xff0c;允许2个请求&#xff0c;其他请求全部拒绝。 不拒绝就可能往db打请求&#xff0c;把db干爆~ interval 1000 rate 2&#xff1b; 一、固定窗口限流 固定窗口限流算法&#xff08;Fixed Window Rate Limiting Algorithm&#xff09;是…...

网工内推 | 国企、港企网工,年底双薪,NA以上认证即可

01 中航期货有限公司 招聘岗位&#xff1a;信息技术部-网络工程师 职责描述&#xff1a; 1、负责总部、分支机构、外联单位网络的日常运维、故障和应急处置&#xff0c;特别是定期监测设备的运行状态&#xff0c;对存在隐患的地方及时发现改正&#xff0c;保持网络稳定通畅&am…...

【华为HCIP | 华为数通工程师】刷题日记1116(一个字惨)

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…...

​软考-高级-系统架构设计师教程(清华第2版)【第7章 系统架构设计基础知识(263~285)-思维导图】​

软考-高级-系统架构设计师教程&#xff08;清华第2版&#xff09;【第7章 系统架构设计基础知识&#xff08;263~285&#xff09;-思维导图】 课本里章节里所有蓝色字体的思维导图...

⑩⑥ 【MySQL】详解 触发器TRIGGER,协助 确保数据的完整性,日志记录,数据校验等操作。

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 触发器 ⑩⑥ 【MySQL】触发器详解1. 什么是触发…...

数据结构与算法编程题3

长度为n的顺序表&#xff0c;删除线性表所有值为x的元素&#xff0c;使得时间复杂度为O(n)&#xff0c;空间复杂度为O(1) #include <iostream> using namespace std;typedef int ElemType; #define Maxsize 100 #define OK 1 #define ERROR 0 typedef struct SqList {E…...

Go基础面经大全(持续补充中)

Go基础 1. 基础特性 Go的优势 天生支持并发&#xff0c;性能高。 单一的标准代码格式&#xff0c;比其他语言更具可读性。 自动垃圾收集机制比Java和Python更有效&#xff0c;因为它与程序同时执行。 Go数据类型 int, string, float, bool, array, slice, map, channel, p…...

uniapp heckbox-group实现多选

文章目录 html 代码JS 代码 混了业务逻辑&#xff0c;谨慎观看 html 代码 <view><!--可滚动视图区域。用于区域滚动 --><scroll-view :style"{ height: clientHeight px }" :scroll-top"scrollTop" scroll-y"true"scrolltouppe…...

读懂:“消费报销”模式新零售打法,适用连锁门店加盟的营销方案

读懂&#xff1a;“消费报销”模式新零售打法&#xff0c;适用连锁门店加盟的营销方案 引言&#xff1a;2023年的双十一已经落下帷幕&#xff0c;作为每年的经典电商促销节&#xff0c;今年已是第15个年头&#xff0c;但是今年各大电商平台却都是非常默契的&#xff0c;没有公布…...

一个基本的http客户端

高可用 客户端 1. httpClient.h #include <iostream> #include <string> #include <functional>class HttpClient { public:HttpClient(std::string url) : url_(url), port_(0) {}int write_http(const std::string &method, const std::string &…...

html-网站菜单-点击菜单展开相应的导航栏,加减号可切换

一、效果图 1.点击显示菜单栏&#xff0c;点击x号关闭&#xff1b; 2.点击一级菜单&#xff0c;展开显示二级&#xff0c;并且加号变为减号&#xff1b; 3.点击其他一级导航&#xff0c;自动收起展开的导航。 二、代码实现 <!DOCTYPE html> <html><head>&…...

2.FastRunner定时任务Celery+RabbitMQ

注意&#xff1a;celery版本和Python冲突问题 不能用高版本Python 用3.5以下&#xff0c;因为项目的celery用的django-celery 3.2.2 python3.7 async关键字 冲突版本 celery3.x方案一&#xff1a; celery3.xpython3.6方案二 &#xff1a; celery4.xpython3.7 解决celery执…...

vb.net 实时监控双门双向门禁控制板源代码

本示例使用设备介绍&#xff1a;实时网络双门双向门禁控制板可二次编程控制网络继电器远程开关-淘宝网 (taobao.com) Imports System.Net.Sockets Imports System.Net Imports System.Text Imports System.ThreadingImports System.Net.NetworkInformation Imports System.Man…...

文具办公产品展示预约小程序的作用如何

从整体来看&#xff0c;文具办公品牌/门店的生意来源于线下自然流量或线上自营商城/入驻第三方商城的的流量&#xff0c;线上多数情况都是以直接销售配送为主&#xff0c;但其实对文具品牌/门店而言还有信息展示、服务预约、在线咨询、产品介绍等需求。 虽然小区周边的消费者需…...

渗透测试流程是什么?7个步骤给你讲清楚!

在学习渗透测试之初&#xff0c;有必要先系统了解一下它的流程&#xff0c;静下心来阅读一下&#xff0c;树立一个全局观&#xff0c;一步一步去建设并完善自己的专业领域&#xff0c;最终实现从懵逼到牛逼的华丽转变。渗透测试是通过模拟恶意黑客的攻击方法&#xff0c;同时也…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

【Java学习笔记】Arrays类

Arrays 类 1. 导入包&#xff1a;import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序&#xff08;自然排序和定制排序&#xff09;Arrays.binarySearch()通过二分搜索法进行查找&#xff08;前提&#xff1a;数组是…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建

制造业采购供应链管理是企业运营的核心环节&#xff0c;供应链协同管理在供应链上下游企业之间建立紧密的合作关系&#xff0c;通过信息共享、资源整合、业务协同等方式&#xff0c;实现供应链的全面管理和优化&#xff0c;提高供应链的效率和透明度&#xff0c;降低供应链的成…...

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

【生成模型】视频生成论文调研

工作清单 上游应用方向&#xff1a;控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

C++.OpenGL (20/64)混合(Blending)

混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制

使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下&#xff0c;限制某个 IP 的访问频率是非常重要的&#xff0c;可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案&#xff0c;使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...

Linux nano命令的基本使用

参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时&#xff0c;显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...