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

相关文章

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核