vue实现相机拍摄,可录视频、拍照片、前置后置切换(简单小demo)

本文主要是介绍vue实现相机拍摄,可录视频、拍照片、前置后置切换(简单小demo),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

内容比较简单,不做过多赘述,只做分享,测试demo,功能有些缺陷,希望路过的大佬多多指正

/(*/ω\*)

<script setup>
import { showToast, showSuccessToast, showFailToast, showLoadingToast } from 'vant';
import { onBeforeMount, onMounted, reactive, ref } from 'vue'const videoArr = ref([])
const odelSel = ref('')//当前使用的摄像头
const myInterval = ref(null)
const mediaStreamTrack = ref('') // 退出时关闭摄像头
const video_stream = ref('') // 视频stream
const recordedBlobs = ref([]) // 视频音频 blobs
const isRecord = ref(false) // 视频是否正在录制
const content = ref('按住拍摄,点击拍照')
let test = 'c'
const startStauts = ref(true)// 开始录制按钮样式
// video参数
const videoRef = ref(null);
// 画布参数(照片回显)
const cs = ref(null)
const css = ref(null)
const csss = ref(null)
const cssss = ref(null)
const csssss = ref(null)// 回显画布宽高
const canWidth = ref('')
const canHeight = ref('')
const echo_Status = ref(false)
const canvas_echo = ref(null)// 关闭摄像头
const closeStatus = ref(true)
// 切换按钮状态
const cutStatus = ref(false)// 照片数量限制
const photoNum = ref([])
// 照片回显数组
const videoList = ref([])
const videoNum = ref(0)
const timeOutEvent = ref(null)
const returns = ref(false)// 录制参数
const isRecording = ref(false)
const videoBlob = ref(null)// 前置后置摄像头切换
const cameraStatu = ref(0)// 预览内容
let contents = ref({});
const showCenter = ref(false)
onMounted(() => {let cedioele = document.getElementsByClassName('camera_video')[0]canWidth.value = cedioele.offsetWidthcanHeight.value = cedioele.offsetHeightlet canvasList = document.getElementsByClassName('canns')
})
// 前置后置切换
const changeDevice = () => {if(cameraStatu.value == 2){cameraStatu.value = 1}else{cameraStatu.value = 2}console.log(666);// 旧版本浏览器可能根本不支持mediaDevices,我们首先设置一个空对象if (navigator.mediaDevices === undefined) {navigator.mediaDevices = {};}console.log(navigator.mediaDevices);navigator.mediaDevices.getUserMedia({video: cameraStatu.value == 1 ? { facingMode: 'user' } : { facingMode: { exact: "environment" } },}).then((stream) => {// 摄像头开启成功console.log(stream);startStauts.value = falsemediaStreamTrack.value = typeof stream.stop === 'function' ? stream : stream.getTracks()[0];video_stream.value = stream;console.log(videoRef);videoRef.value.srcObject = stream;videoRef.value.play();closeStatus.value = false}).catch(err => {console.log(err);});console.log(cameraStatu.value);
}
// ------------------------------摄像头开关按钮-------------------------------
// 开启摄像头事件
const getCamera = () => {cameraStatu.value = 1console.log(cameraStatu.value);navigator.mediaDevices.getUserMedia({audio: true,video: { facingMode: { exact: "environment" } },}).then((stream) => {// 摄像头开启成功startStauts.value = falsemediaStreamTrack.value = typeof stream.stop === 'function' ? stream : stream.getTracks()[0];video_stream.value = stream;console.log(videoRef);videoRef.value.srcObject = stream;videoRef.value.play();}).catch((err) => {console.log(err);});console.log(cameraStatu.value);
}
// 关闭摄像头
const closeCamera = () => {if (!videoRef.value.srcObject) return;let stream = videoRef.value.srcObject;let tracks = stream.getTracks();tracks.forEach(track => {track.stop();});videoRef.value.srcObject = null;startStauts.value = truecloseStatus.value = true;
}
// ==========================点击拍照============================
const shoot = () => {if (startStauts.value == true) {showFailToast('请打开摄像头');} else {if (videoList.value.length < 5) {videoList.value.push({val: videoRef.value,id: videoNum.value++,type: 1})console.log(videoList.value);test = test += 's'photoNum.value.push(test)console.log(photoNum.value);showLoadingToast({message: '处理中...',forbidClick: true,});setTimeout(() => {console.log('拍单张');console.log(videoList.value.length);console.log(cs.value);let sc = cs.value[videoList.value.length - 1].getContext('2d');sc.drawImage(videoList.value[videoList.value.length - 1].val, 0, 0, 50, 50);}, 700)} else {showFailToast('照片已达上限');}}
}
// 检测点击按钮事件定时器
let times = null;
function echo_btn(index) {console.log(index);console.log(videoList.value);if (videoList.value[index].type == 1) {echo_Status.value = truecloseStatus.value = truecutStatus.value = truestartStauts.value = falseconst image = cs.value[index].toDataURL("image/png");contents = {index: videoNum.value++,type: 1,url: image}showCenter.value = trueconsole.log(image);} else {echo_Status.value = truecloseStatus.value = truecutStatus.value = truestartStauts.value = falsecontents = {index: videoNum.value++,type: 2,url: videoList.value[index].val}showCenter.value = true}
}
// 用户是否长按 false 点击、ture 长按
let userStatus = ref(false);
const currentNum = ref(0)
// 手指点击触发
const photosStart = () => {console.log(videoList.value.length);if (startStauts.value == true) {showFailToast('请打开摄像头');} else {// 判断是否超过5个if (videoList.value.length > 5) {showFailToast("最多录制5个文件!");return;}times = setTimeout(() => {userStatus.value = true;pressEvenet();}, 500);}};
// 长按录像
let mediaRecorder = null;
let inte = null;
let eouts = null;
let stops = false;
const pressEvenet = () => {console.log(videoList.value);if (videoList.value.length < 5) {currentNum.value = 0;let chunks = [];stops = true;mediaRecorder = new MediaRecorder(video_stream.value, {mimeType: "video/webm;codecs=vp9",});mediaRecorder.ondataavailable = (event) => {if (event.data && event.data.size > 0) {chunks.push(event.data);}};mediaRecorder.onstop = () => {const blob = new Blob(chunks, { type: "video/webm" });const url = URL.createObjectURL(blob);console.log(url);videoList.value.push({val: url,id: videoNum.value++,type: 2})chunks = [];};mediaRecorder.start();inte = setInterval(() => {currentNum.value++;if (currentNum.value >= 100) {showDeleteButton()}}, 100);eouts = setTimeout(() => {clearInterval(inte);showSuccessToast("录制完成");mediaRecorder.stop();stops = false;}, 10000);} else {showFailToast("最多录制5个文件!");}};
const showDeleteButton = () => {currentNum.value = 0// 判断是否超过5个if (videoList.value.length > 5) {showFailToast("最多录制5个文件!");return;}clearTimeout(times);pressStop();
}// 停止录制事件
const pressStop = () => {if (stops) {mediaRecorder.stop();showSuccessToast("录制完成");stops = false;clearInterval(inte);clearTimeout(eouts);}
};function close(index) {console.log(666);videoList.value.splice(index, 1)
}</script><template><div class="camera_box"><!-- 成像区域 --><div class="image_box"><!-- <vue-camera ref="camera"></vue-camera> --><video ref="videoRef" autoplay width="100%" height="100%" class="camera_video"></video><!-- <canvas class="canvas_echo" ref="canvas_echo" :width="canWidth" :height="canHeight" v-show="echo_Status"></canvas> --><van-button type="success" class="start_live" v-show="startStauts" @click="getCamera">开启摄像头</van-button><van-button type="success" class="end_live" @click="closeCamera" v-show="!closeStatus">关闭摄像头</van-button><!-- 顶部装饰按钮 --><div class="btn_box"><div><van-button round size="mini" color="#000" class="back_btn"><van-icon name="arrow-left" /></van-button></div><div><van-button type="success" size="mini" class="success_btn">完成</van-button></div></div></div><!-- 图片预览列表 --><div class="image_list"><!-- 加按钮 --><div class="add_btn"><img src="../src/img/add.png" alt=""></div><!-- 列表 --><div class="list"><!-- v-for="(index,item) of photoNum" :key="index" --><div class="list_item" @click.stop.prevent="echo_btn(index)" v-for="(item, index) in videoList"><canvas class="canns" ref="cs" width="50" height="50"></canvas><div class="clack" v-if="item.type == 2"><img src="../src/img/vedio.png" alt=""></div><div class="close_btn"><img src="../src//img/close.png" alt="" @click.stop.prevent="close(index)"></div></div></div><!-- 减按钮 --><div class="add_btn"><img src="../src/img/clear.png" alt=""></div></div><!-- 拍摄操作区域 --><div class="btn_boxs"><!-- 图片按钮 --><input type="file" id="file"><label for="file" class="image_go"><div><van-icon name="photo-o" size="24px" color="#FA9923" /></div></label><!-- 拍摄按钮 --><div class="pach_box" @click.stop.prevent="shoot" @touchstart="photosStart" @touchend="showDeleteButton"><van-button plain class="pach_btn"><img class="pach_img" src="../src/img/live.png" alt=""></van-button></div><!-- 前置后置切换 --><div class="camera_go" @click='changeDevice'><van-icon name="photograph" size="24px" color="#FA9923" /></div><div class="pach_boxs"><van-circle v-model:current-rate="currentRate" :rate="currentNum" :speed="100" :text="text" size="80px"color="#FA9923" stroke-width="100" /></div></div><div class="message"><b>{{ content }}</b></div><van-popup v-model:show="showCenter"><img :src="contents.url" alt="" v-if="contents.type == 1" :style="{ width: '70vw', height: 'auto' }"><video :src="contents.url" v-if="contents.type == 2" controls autoplay:style="{ width: '70vw', height: 'auto' }"></video></van-popup></div>
</template><style scoped>
.close_btn {position: absolute;top: 0;right: 0;z-index: 10;width: 0;height: 0;border-top: 16px solid #EEEEEE;border-left: 16px solid transparent;display: flex;justify-content: center;align-items: center;
}.close_btn>img {width: 8px;height: 8px;position: absolute;top: -13px;right: 1px;
}.clack {width: 100%;height: 100%;position: absolute;top: 0;left: 0;
}.clack>img {width: 100%;height: 100%;
}.canvas_echo {position: absolute;top: 0;left: 0;
}.camera_box {width: calc(100vw);height: 100vh;padding: 10px;
}.success_btn {padding: 10px;
}.start_live {padding: 10px;position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);z-index: 5;
}.end_live {padding: 10px;position: absolute;bottom: 0;right: 0;transform: translate(-50%, -50%);z-index: 5;
}.cut_live {padding: 10px;position: absolute;bottom: 0;left: 0;z-index: 5;
}.image_box {width: 100%;height: 60vh;position: relative;border-radius: 0 0 10px 10px;border: 1px solid #EEEEEE;
}.live_window {width: 100%;}.back_btn {width: 20px;height: 20px;border: 50%;opacity: 0.5;
}.btn_box {width: 100%;display: flex;padding: 10px;justify-content: space-between;position: absolute;top: 0;font-size: 1rem;
}.image_list {width: 100%;height: 50px;margin-top: 10px;display: flex;justify-content: center;align-items: center;
}.btn_boxs {width: 100%;height: 100px;margin-top: 20px;display: flex;align-items: center;justify-content: center;position: relative;
}.image_go {width: 20%;
}.camera_go {width: 20%;display: flex;justify-content: right;
}.pach_box {border-radius: 50%;}.pach_boxs {width: 100%;height: 100%;position: absolute;border-radius: 50%;z-index: -1;display: flex;justify-content: center;align-items: center;
}.pach_icon {width: 80%;height: 80%;
}.pach_btn {width: 70px;height: 70px;border-radius: 50%;display: flex;justify-content: center;align-items: center;
}.pach_img {width: 34px;height: 34px;
}.add_btn {width: 20px;height: 20px;background-color: #FA9A24;display: flex;justify-content: center;align-items: center;border-radius: 5px;margin-left: 10px;
}.add_btn>img {width: 12px;height: 12px;
}.list {display: flex;align-items: center;
}.list_item {width: 50px;height: 50px;background-color: #EEEEEE;margin-left: 10px;border-radius: 8px;overflow: hidden;position: relative;
}#file {display: none;
}.message {margin-top: 30px;width: 100%;display: flex;justify-content: center;align-items: center;
}
</style>

这篇关于vue实现相机拍摄,可录视频、拍照片、前置后置切换(简单小demo)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/863418

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

部署Vue项目到服务器后404错误的原因及解决方案

《部署Vue项目到服务器后404错误的原因及解决方案》文章介绍了Vue项目部署步骤以及404错误的解决方案,部署步骤包括构建项目、上传文件、配置Web服务器、重启Nginx和访问域名,404错误通常是... 目录一、vue项目部署步骤二、404错误原因及解决方案错误场景原因分析解决方案一、Vue项目部署步骤

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque

Python如何实现PDF隐私信息检测

《Python如何实现PDF隐私信息检测》随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要,本文将介绍如何使用Python检测PDF文件中的隐私信息,需要的可以参考下... 目录项目背景技术栈代码解析功能说明运行结php果在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景