vue实现相机拍摄,可录视频、拍照片、前置后置切换(简单小demo)
vue
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
项目地址:https://gitcode.com/gh_mirrors/vu/vue
免费下载资源
·
内容比较简单,不做过多赘述,只做分享,测试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.offsetWidth
canHeight.value = cedioele.offsetHeight
let 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 = false
mediaStreamTrack.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 = 1
console.log(cameraStatu.value);
navigator.mediaDevices
.getUserMedia({
audio: true,
video: { facingMode: { exact: "environment" } },
})
.then((stream) => {
// 摄像头开启成功
startStauts.value = false
mediaStreamTrack.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 = true
closeStatus.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 = true
closeStatus.value = true
cutStatus.value = true
startStauts.value = false
const image = cs.value[index].toDataURL("image/png");
contents = {
index: videoNum.value++,
type: 1,
url: image
}
showCenter.value = true
console.log(image);
} else {
echo_Status.value = true
closeStatus.value = true
cutStatus.value = true
startStauts.value = false
contents = {
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>
GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5
* chore: fix link broken
Signed-off-by: snoppy <michaleli@foxmail.com>
* Update packages/template-compiler/README.md [skip ci]
---------
Signed-off-by: snoppy <michaleli@foxmail.com>
Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891
Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
更多推荐
已为社区贡献5条内容
所有评论(0)