使用 Vue3 实现摄像头拍照功能

参考资料:MediaDevices.getUserMedia() - Web API | MDN

重要navigator.mediaDevices.getUserMedia 需要在安全的上下文中运行。现代浏览器要求摄像头和麦克风的访问必须通过 HTTPS 或 localhost(被视为安全的本地环境)进行,如果上传服务器地址是http开头的,而不是https,可以使用免费的Let's Encrypt打开,例子:进入宝塔-点击网络-点击自己的的链接-点击ssl-选择Let's Encrypt配置就好

模板部分

  • <video> 标签

    • 作用:用于显示摄像头捕获的视频流。
    • 属性
      • ref="videoRef":通过 ref 获取该 DOM 元素的引用,以便在脚本中操作。
      • autoplay:自动播放视频流,无需用户手动点击播放。
      • playsinline:在移动设备上防止视频自动全屏播放,保持在页面内嵌显示。
  • 拍摄按钮

    • 使用一个 <div> 元素作为按钮,并绑定 @click="shoot" 事件,当用户点击按钮时触发 shoot 方法。
    • 内部嵌套一个小的 <div class="shoot_buttons"></div>,用于实现按钮的视觉效果
  • 显示捕获的图片

    • 使用一个 <div class="images"> 容器包裹所有捕获的图片。
    • 通过 v-for="(img, index) in images" 指令遍历 images 数组,为每个图片生成一个 <img> 元素。
    • :key="index":为每个循环生成的元素提供唯一的键值,优化渲染性能。
    • :src="img":绑定图片的来源为数组中的每个 Data URL。
<template>
  <div class="container">
    <!-- 视频元素,用于显示摄像头捕获的视频流 -->
    <video ref="videoRef" autoplay playsinline></video>
    <!-- 拍摄按钮 -->
    <div @click="shoot" class="shoot_button">
      <div class="shoot_buttons"></div>
    </div>
    <!-- 循环显示所有捕获的图片 -->
    <div class="images">
      <img v-for="(img, index) in images" :key="index" :src="img" />
    </div>
  </div>
</template>

js部分

  • 引入 Vue 的响应式 API

    • ref:用于创建响应式的数据引用。
    • onMounted:生命周期钩子,在组件挂载完成后执行相应的操作。
  • 获取视频 DOM 元素的引用

    • const videoRef = ref(null);:通过 ref 获取 <video> 元素,以便在脚本中操作其 srcObject 属性,显示视频流。
  • 定义存储图片的数组

    • const images = ref([]);:创建一个响应式数组,用于存储所有拍摄的图片的 Data URL。
  • 计数器限制拍摄数量

    • let count = 0;:定义一个计数器,限制用户最多只能拍摄 10 张照片。
    • shoot 方法中,每拍摄一张照片,计数器加一,当计数器超过 9 时,弹出警告并停止拍摄。
  • 启动摄像头

    • startCamera 函数使用 navigator.mediaDevices.getUserMedia 请求用户的摄像头权限。
    • 请求成功后,将获取到的 MediaStream 赋值给视频元素的 srcObject,从而在页面上显示视频流。
    • 捕获异常并在控制台输出错误信息,以便调试和提示用户。
  • 组件挂载后启动摄像头

    • onMounted 钩子确保在组件挂载完成后立即调用 startCamera,启动摄像头。
  • 实现拍摄功能

    • shoot 方法在用户点击拍摄按钮时触发。
    • 检查拍摄次数是否超过限制,若超过则弹出警告并返回。
    • 获取当前视频流的帧,通过创建一个 <canvas> 元素,将视频帧绘制到 canvas 上。
    • 使用 canvas.toDataURL("image/png")canvas 内容转换为 Base64 编码的图片 URL。
    • 将生成的 Data URL 推入 images 数组,触发页面上图片的循环显示。
<script setup>
import { ref, onMounted } from "vue";

// 获取存储的DOM节点
const videoRef = ref(null);

// 定义 images 用于存储捕获的图片数组
const images = ref([]);

// 计数器,用于限制拍摄的图片数量
let count = 0;

// 定义一个函数来请求用户的摄像头权限并在成功后设置视频流
const startCamera = async () => {
  try {
    // 请求用户媒体(仅视频)
    const stream = await navigator.mediaDevices.getUserMedia({
      video: true,
      audio: false,
    });
    // 将获取到的 MediaStream 赋值给视频元素的 srcObject
    videoRef.value.srcObject = stream;
  } catch (error) {
    console.error("无法访问摄像头", error);
  }
};

// 当组件挂载完成后启动摄像头
onMounted(() => {
  startCamera();
});

// 点击拍摄
const shoot = () => {
  if (count > 9) {
    alert("最多只能拍摄10张照片");
    return;
  }
  count++;
  const video = videoRef.value;
  if (!video) return;

  // 创建一个canvas元素
  const canvas = document.createElement("canvas");
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;

  // 在canvas上绘制当前的视频帧
  const ctx = canvas.getContext("2d");
  ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

  // 将canvas内容转换为Data URL并推入 images 数组
  const dataURL = canvas.toDataURL("image/png");
  images.value.push(dataURL);
};
</script>

 样式部分

<style scoped>
/* 容器样式 */
.container {
  font-family: Arial, sans-serif; /* 设置字体 */
  text-align: center; /* 文本居中 */
  margin-top: 50px; /* 顶部外边距 */
}

/* 视频元素样式 */
video {
  width: 300px; /* 宽度 */
  height: auto; /* 高度自动调整以保持视频的原始比例 */
  background-color: #000; /* 背景颜色为黑色,防止视频加载前显示空白 */
  border: 1px solid #ccc; /* 灰色边框 */
}

/* 拍摄按钮样式 */
.shoot_button {
  margin-top: 10px;
  width: 60px;
  height: 60px;
  border: 4px solid red;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  cursor: pointer;
  background-color: white;
}

.shoot_buttons {
  width: 30px;
  height: 30px;
  background-color: red;
  border-radius: 50%;
}

/* 图片容器样式 */
.images {
  width: 850px;
  height: auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 20px;
  border: 2px gray solid;
  padding: 10px;
  box-sizing: border-box;
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: 20px;
  background-color: rgba(255, 255, 255, 0.8);
}

/* 图片样式 */
img {
  width: 70px;
  height: 80px;
  margin: 10px;
  border: 1px solid #ccc;
  object-fit: cover;
}
</style>

代码功能总结

  1. 摄像头访问与视频流显示

    • 通过 navigator.mediaDevices.getUserMedia 请求用户的摄像头权限。
    • 成功后,将获取到的 MediaStream 赋值给 <video> 元素的 srcObject,实时显示摄像头捕获的视频流。
  2. 拍摄照片

    • 用户点击拍摄按钮时,触发 shoot 方法。
    • 方法内部创建一个 <canvas> 元素,将当前视频帧绘制到 canvas 上。
    • canvas 内容转换为 Base64 编码的 Data URL,并推入 images 数组中。
  3. 图片展示与数量限制

    • images 数组存储所有拍摄的照片,通过 v-for 循环在页面上显示。
    • 使用计数器 count 限制最多拍摄 10 张照片,防止内存占用过高。
  4. 界面样式与布局

    • 使用 CSS 样式美化视频、按钮和图片的展示。
    • 确保界面在不同设备上有良好的用户体验,特别是在移动设备上的布局调整。

完整代码 

<template>
  <div class="container">
    <!-- 视频元素,用于显示摄像头捕获的视频流 -->
    <video ref="videoRef" autoplay playsinline></video>
    <!-- 按钮 -->
    <div @click="shoot" class="shoot_button">
      <div class="shoot_buttons"></div>
    </div>
    <!-- 循环显示所有捕获的图片 -->
    <div class="images">
      <img v-for="(img, index) in images" :key="index" :src="img" />
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from "vue";
// 获取存储的DOM节点
const videoRef = ref(null);
// 定义 images 用于存储捕获的图片数组
const images = ref([]);
// 计数
let count = 0;
// 定义一个函数来请求用户的摄像头权限并在成功后设置视频流
const startCamera = async () => {
  try {
    // 请求用户媒体(仅视频)
    const stream = await navigator.mediaDevices.getUserMedia({
      video: true,
      audio: false,
    });
    videoRef.value.srcObject = stream;
  } catch (error) {
    console.error("无法访问摄像头", error);
  }
};

// 当组件挂载完成后启动摄像头
onMounted(() => {
  startCamera();
});

// 点击拍摄
const shoot = () => {
  if (count > 9) {
    alert("最多只能拍摄10张照片");
    return;
  }
  count++;
  const video = videoRef.value;
  if (!video) return;
  // 创建一个canvas元素
  const canvas = document.createElement("canvas");
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;

  // 在canvas上绘制当前的视频帧
  const ctx = canvas.getContext("2d");
  ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

  // 将canvas内容转换为Data URL并推入 images 数组
  const dataURL = canvas.toDataURL("image/png");
  images.value.push(dataURL);
};
</script>

<style scoped>
/* 容器样式 */
.container {
  font-family: Arial, sans-serif; /* 设置字体 */
  text-align: center; /* 文本居中 */
  margin-top: 50px; /* 顶部外边距 */
}

/* 视频元素样式 */
video {
  width: 300px; /* 宽度 */
  height: auto; /* 高度自动调整以保持视频的原始比例 */
  background-color: #000; /* 背景颜色为黑色,防止视频加载前显示空白 */
  border: 1px solid #ccc; /* 灰色边框 */
}

/* 按钮样式 */
.shoot_button {
  margin-top: 10px;
  width: 30px;
  height: 30px;
  border: 2px solid red;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
}
.shoot_buttons{
  width: 20px;
  height: 20px;
  background-color: red;
  border-radius: 50%;
}
/* 图片容器样式 */
.images {
  width: 850px;
  height: 100px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 200px;
  border: 2px gray solid;
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
}

/* 图片样式 */
img {
  width: 70px;
  height: 80px;
  margin-right: 10px;
  margin-top: 10px;
}
</style>

效果图 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024安装hexo和next并部署到github和服务器最新教程

碎碎念 本来打算写点算法题上文所说的题目&#xff0c;结果被其他事情吸引了注意力。其实我之前也有过其他博客网站&#xff0c;但因为长期不维护&#xff0c;导致数据丢失其实是我懒得备份。这个博客现在部署在GitHub Pages上&#xff0c;github不倒&#xff0c;网站不灭&…

RTMP推流平台EasyDSS在无人机推流直播安防监控中的创新应用

无人机与低空经济的关系密切&#xff0c;并且正在快速发展。2024年中国低空经济行业市场规模达到5800亿元&#xff0c;其中低空制造产业占整个低空经济产业的88%。预计未来五年复合增速将达到16.03%。 随着科技的飞速发展&#xff0c;公共安防关乎每一个市民的生命财产安全。在…

java全栈day16--Web后端实战(数据库)

一、数据库介绍 二、Mysql安装&#xff08;自行在网上找&#xff0c;教程简单&#xff09; 安装好了进行Mysql连接 连接语法&#xff1a;winr输入cmd&#xff0c;在命令行中再输入mysql -uroot -p密码 方法二&#xff1a;winr输入cmd&#xff0c;在命令行中再输入mysql -uroo…

基于注意力的几何感知的深度学习对接模型 GAABind - 评测

GAABind 作者是苏州大学的生物基础与医学院, 期刊是 Briefings in Bioinformatics, 2024, 25(1), 1–14。GAABind 是一个基于注意力的几何感知蛋白-小分子结合模式与亲和力预测模型,可以捕捉小分子和蛋白的几何、拓扑结构特征以及相互作用。使用 PDBBind2020 和 CASF2016 作…

【CSS in Depth 2 精译_080】 13.1:CSS 渐变效果(中)——不同色彩空间的颜色插值算法在 CSS 渐变中的应用

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第四部分 视觉增强技术 ✔️【第 13 章 渐变、阴影与混合模式】 ✔️ 13.1 渐变 ✔️ 13.1.1 使用多个颜色节点&#xff08;上&#xff09;13.1.2 颜色插值方法&#xff08;中&#xff09; ✔️13.1…

虚拟机VirtualBox安装最新版本Oracle数据库

https://www.oracle.com/database/technologies/databaseappdev-vm.html 如上所示&#xff0c;从Oracle官方网站上下载最新版本的VirtualBox虚拟机对应的Oracle数据库安装源文件。 如上所示&#xff0c;在VirtualBox中导入下载的Oracle安装源文件。 如上所示&#xff0c;导入…

热更新解决方案4——xLua热补丁

概述 运行时不在执行C#中的代码&#xff0c;而是执行Lua中的代码&#xff0c;相当于是打了个补丁。 1.第一个热补丁 2.多函数替换 3.协程函数替换 在原HotfixMain脚本中只加个协程函数即可&#xff08;和在Start中启动协程函数&#xff09; 4.索引器和属性替换 在HotfixMain中…

突破长链视觉推理瓶颈:Insight-V多智能体架构解析

GitHub 仓库&#xff1a;https://github.com/dongyh20/Insight-V HuggingFace 模型库&#xff1a;https://huggingface.co/THUdyh/Insight-V arXiv 技术论文&#xff1a;https://arxiv.org/pdf/2411.14432 模型&#xff1a;https://huggingface.co/THUdyh/Insight-V-Reason 今天…

IDEA 未启用lombok插件的Bug

项目中maven已引用了lombok依赖&#xff0c;之前运行没有问题的&#xff0c;但有时启动会提示&#xff1a; java: You arent using a compiler supported by lombok, so lombok will not work and has been disabled. Your processor is: com.sun.proxy.$Proxy8 Lombok support…

AI工具如何深刻改变我们的工作与生活

在当今这个科技日新月异的时代&#xff0c;人工智能&#xff08;AI&#xff09;已经从科幻小说中的概念变成了我们日常生活中不可或缺的一部分。从智能家居到自动驾驶汽车&#xff0c;从医疗诊断到金融服务&#xff0c;AI正以惊人的速度重塑着我们的世界。 一、工作方式的革新…

压力测试Jmeter简介

前提条件&#xff1a;要安装JDK 若不需要了解&#xff0c;请直接定位到左侧目录的安装环节。 1.引言 在现代软件开发中&#xff0c;性能和稳定性是衡量系统质量的重要指标。为了确保应用程序在高负载情况下仍能正常运行&#xff0c;压力测试变得尤为重要。Apache JMeter 是一…

手眼标定工具操作文档

1.手眼标定原理介绍 术语介绍 手眼标定&#xff1a;为了获取相机与机器人坐标系之间得位姿转换关系&#xff0c;需要对相机和机器人坐标系进行标定&#xff0c;该标定过程成为手眼标定&#xff0c;用于存储这一组转换关系的文件称为手眼标定文件。 ETH&#xff1a;即Eye To …

vue 自定义组件image 和 input

本章主要是介绍自定义的组件&#xff1a;WInput&#xff1a;这是一个验证码输入框&#xff0c;自动校验&#xff0c;输入完成回调等&#xff1b;WImage&#xff1a;这是一个图片展示组件&#xff0c;集成了缩放&#xff0c;移动等操作。 目录 一、安装 二、引入组件 三、使用…

CTFHUB-web(SSRF)

内网访问 点击进入环境&#xff0c;输入 http://127.0.0.1/flag.php 伪协议读取文件 /?urlfile:///var/www/html/flag.php 右击查看页面源代码 端口扫描 1.根据题目提示我们知道端口号在8000-9000之间,使用bp抓包并进行爆破 POST请求 点击环境&#xff0c;访问flag.php 查看页…

Mysql 深度分页查询优化

Mysql 分页优化 1. 问题根源 问题&#xff1a; mysql在数据量大的时候&#xff0c;深度分页数据偏移量会增大&#xff0c;导致查询效率越来越低。 问题根源&#xff1a; 当使用 LIMIT 和 OFFSET 进行分页时&#xff0c;MySQL 必须扫描 OFFSET LIMIT 行&#xff0c;然后丢弃前…

[LeetCode-Python版]21. 合并两个有序链表(迭代+递归两种解法)

题目 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#x…

Git 安装教程

Git 是一个分布式版本控制系统&#xff0c;用于跟踪源代码的变化。它允许多个开发者协作开发同一个项目&#xff0c;能够有效管理项目的版本历史&#xff0c;便于协作与代码回溯。 Git官网 官网提供各种操作系统的安装程序。 step1.点击"Download for Windows"按钮&a…

Spring学习笔记-基础

前言&#xff1a;我是在哔哩哔哩上黑马程序员上找的课程。-----2024-12-16 官网Spring | Homehttps://spring.io/ Sping全家桶中重要三个&#xff1a; Spring Framework底层框架&#xff0c;在整个全家通中&#xff0c;所有的技术依赖它执行。 Spring Boot简化开发加速开发…

CNAS-AL06《实验室认可领域分类》修订,软件测试领域整体修订

为了不断适应行业发展的需要&#xff0c;进一步完善认可评审管理工作&#xff0c;进一步提高认可评审工作质量&#xff0c;CNAS认可委针对CNAS-AL06《实验室认可领域分类》进行了修订&#xff0c;并于近日正式发布。 原文件CNAS-AL06:20220101有25项一级代码&#xff0c;其中0…

单片机原理及应用笔记:单片机中断系统原理与项目实践

高金鹏&#xff1a;男&#xff0c;银川科技学院计算机与人工智能学院&#xff0c;2022级别计算机科学与技术本科生&#xff0c;单片机原理及应用课程第六组。 指导教师&#xff1a;王兴泽 电子邮件&#xff1a;高金鹏3535558665qq.com 个人CSDN:暴躁的海绵宝宝 暴躁的海绵宝…