前端调取摄像头并实现拍照功能
前言
最近接到的一个需求十分有意思,设计整体实现了前端仿 微信扫一扫 的功能。整理了一下思路,做一个分享。
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 请务必注意)

- 现在你应该会看到摄像头已经在屏幕上展示了
三. 截取当前画面
-
这里我随手写了一个按钮当作拍摄键,接下来我们将实现点击这个按钮截取当前画面。
-
这里你需要知道一个前提,虽然我们现在看到的视频是连贯的,但其实在浏览器渲染的时候,它其实是一帧一帧渲染的。就像宫崎骏有些动漫一样,是一张一张手写画。
-
让我们打开 Performance 标签卡,记录一下打开掘金首页的过程,可以看到浏览器的整个渲染过程其实也是一帧一帧拼接到一起,才完成了整个页面的渲染。

-
知道了这个前提,那么举一反三,我们就可以明白,虽然我们现在已经打开了摄像头,看到的视频好像是在连贯展示,但其实它也是一帧一帧拼到一起的。那现在我们要做的事情就非常明了,当我按下按钮的时候,想办法将 video 标签当前的画面保存下来。
-
这里不是特别容易想到,我就直接说答案了,在这个场景,我们需要用到 canvas 的一些能力。不要害怕,我目前对 canvas 的使用也不是特别熟练,今天也不会用到特别复杂的功能。
-
首先创建一个空白的 canvas 元素,元素的宽高设置为和 video 标签一致。

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

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

-
感觉参数有点多?没关系,我们再精简一下,我们只需要考虑第二个用法,也就是5参数的写法。(sx,sy 是做裁切用到的,本文用不到,感兴趣可以自行了解。)
-
这里先简单解释一下 dx 和 dy 是什么意思。在 canvas 里也存在一个看不见的坐标系,起点也是左上角。设想你想在一个 HTML 的 body 元素里写一个距离左边距离
100px距离顶部100px的画面,是不是得写margin-left:100px margin-top:100px这样的代码?没错,这里的 dy 和 dx 也是同样的道理。 -
我们再看
dwidth,和dheight,从这个名字你就能才出来,肯定和我们将要在画笔里画画的元素的宽度和高度有关,是的,你猜的没错,它就好像你设置一个 div 元素的高度和宽度一样,代表着你将在画布上画的截图的宽高属性。 -
现在只剩下第一个参数还没解释,这里直接说答案,我们可以直接将
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>
相关文章:
前端调取摄像头并实现拍照功能
前言 最近接到的一个需求十分有意思,设计整体实现了前端仿 微信扫一扫 的功能。整理了一下思路,做一个分享。 tips: 如果想要实现完整扫一扫的功能,你需要掌握一些前置知识,这次我们先讲如何实现拍照并且保存的功能。 一. wind…...
android —— 阴影效果和跑马灯效果Textview
1、带阴影的TextView ①、 android:shadowColor“color/black” 设置阴影颜色,需要与shadowRadius一起使用 ②、android:shadowRadius“3.0” 设置阴影模糊程度,设为0.1会变成字体颜色,建议设置3.0 ③、android:shadowDx“10” 设置阴影在水…...
多态语法详解
多态语法详解 一:概念1:多态实现条件 二:重写:三:向上转型和向下转型1:向上转型:1:直接赋值:2:方法传参3:返回值 2:向下转型 一:概念 1:同一个引…...
Python大数据之linux学习总结——day11_ZooKeeper
ZooKeeper ZK概述 ZooKeeper概念: Zookeeper是一个分布式协调服务的开源框架。本质上是一个分布式的小文件存储系统 ZooKeeper作用: 主要用来解决分布式集群中应用系统的一致性问题。 ZooKeeper结构: 采用树形层次结构,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毫秒内,允许2个请求,其他请求全部拒绝。 不拒绝就可能往db打请求,把db干爆~ interval 1000 rate 2; 一、固定窗口限流 固定窗口限流算法(Fixed Window Rate Limiting Algorithm)是…...
网工内推 | 国企、港企网工,年底双薪,NA以上认证即可
01 中航期货有限公司 招聘岗位:信息技术部-网络工程师 职责描述: 1、负责总部、分支机构、外联单位网络的日常运维、故障和应急处置,特别是定期监测设备的运行状态,对存在隐患的地方及时发现改正,保持网络稳定通畅&am…...
【华为HCIP | 华为数通工程师】刷题日记1116(一个字惨)
个人名片: 🐼作者简介:一名大三在校生,喜欢AI编程🎋 🐻❄️个人主页🥇:落798. 🐼个人WeChat:hmmwx53 🕊️系列专栏:🖼️…...
软考-高级-系统架构设计师教程(清华第2版)【第7章 系统架构设计基础知识(263~285)-思维导图】
软考-高级-系统架构设计师教程(清华第2版)【第7章 系统架构设计基础知识(263~285)-思维导图】 课本里章节里所有蓝色字体的思维导图...
⑩⑥ 【MySQL】详解 触发器TRIGGER,协助 确保数据的完整性,日志记录,数据校验等操作。
个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客 学习社区:进去逛一逛~ 触发器 ⑩⑥ 【MySQL】触发器详解1. 什么是触发…...
数据结构与算法编程题3
长度为n的顺序表,删除线性表所有值为x的元素,使得时间复杂度为O(n),空间复杂度为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的优势 天生支持并发,性能高。 单一的标准代码格式,比其他语言更具可读性。 自动垃圾收集机制比Java和Python更有效,因为它与程序同时执行。 Go数据类型 int, string, float, bool, array, slice, map, channel, p…...
uniapp heckbox-group实现多选
文章目录 html 代码JS 代码 混了业务逻辑,谨慎观看 html 代码 <view><!--可滚动视图区域。用于区域滚动 --><scroll-view :style"{ height: clientHeight px }" :scroll-top"scrollTop" scroll-y"true"scrolltouppe…...
读懂:“消费报销”模式新零售打法,适用连锁门店加盟的营销方案
读懂:“消费报销”模式新零售打法,适用连锁门店加盟的营销方案 引言:2023年的双十一已经落下帷幕,作为每年的经典电商促销节,今年已是第15个年头,但是今年各大电商平台却都是非常默契的,没有公布…...
一个基本的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.点击显示菜单栏,点击x号关闭; 2.点击一级菜单,展开显示二级,并且加号变为减号; 3.点击其他一级导航,自动收起展开的导航。 二、代码实现 <!DOCTYPE html> <html><head>&…...
2.FastRunner定时任务Celery+RabbitMQ
注意:celery版本和Python冲突问题 不能用高版本Python 用3.5以下,因为项目的celery用的django-celery 3.2.2 python3.7 async关键字 冲突版本 celery3.x方案一: celery3.xpython3.6方案二 : celery4.xpython3.7 解决celery执…...
vb.net 实时监控双门双向门禁控制板源代码
本示例使用设备介绍:实时网络双门双向门禁控制板可二次编程控制网络继电器远程开关-淘宝网 (taobao.com) Imports System.Net.Sockets Imports System.Net Imports System.Text Imports System.ThreadingImports System.Net.NetworkInformation Imports System.Man…...
文具办公产品展示预约小程序的作用如何
从整体来看,文具办公品牌/门店的生意来源于线下自然流量或线上自营商城/入驻第三方商城的的流量,线上多数情况都是以直接销售配送为主,但其实对文具品牌/门店而言还有信息展示、服务预约、在线咨询、产品介绍等需求。 虽然小区周边的消费者需…...
渗透测试流程是什么?7个步骤给你讲清楚!
在学习渗透测试之初,有必要先系统了解一下它的流程,静下心来阅读一下,树立一个全局观,一步一步去建设并完善自己的专业领域,最终实现从懵逼到牛逼的华丽转变。渗透测试是通过模拟恶意黑客的攻击方法,同时也…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
