当前位置: 首页 > news >正文

实现微信小程序的UniApp相机组件:拍照、录像与双指缩放

在微信小程序开发中,相机功能已成为许多应用的核心组成部分。本文将介绍如何使用UniApp框架实现一个功能丰富的相机组件,支持拍照、录像、前后摄像头切换以及双指缩放等功能。

功能概述

  • 这个相机组件具备以下核心功能:

  • 拍照功能:支持高质量图片拍摄

  • 录像功能:支持最长60秒的视频录制

  • 前后摄像头切换:轻松切换前置和后置摄像头

  • 双指缩放:通过手势控制相机变焦

  • 操作模式切换:在拍照和录像模式间流畅切换

    实现细节

    相机基础设置

    首先,我们使用UniApp的camera组件作为基础

    双指缩放实现

    双指缩放是通过监听触摸事件并计算两指之间的距离变化来实现的

    模式切换与媒体捕获

    通过滑动切换拍照和录像模式,并分别实现拍照和录像功能

    用户界面设计

    组件界面采用黑色主题,符合相机应用的常见设计风格

    样式设计

    使用SCSS编写样式,确保界面美观且响应式

    完整代码如下

<template><view class="container"><camera:device-position="cameraType"flash="off"@error="handleCameraError"@touchstart="handleZoomStart"@touchmove="handleZoomMove"style="width: 100%; height: 75vh"ref="cameraRef":resolution="'high'"></camera><!-- 缩放级别显示(可选) --><view class="zoom-indicator">缩放: {{ currentZoom.toFixed(1) }}x</view><view class="btn_group"><viewclass="top"@touchstart="handleTouchStart"@touchmove="handleTouchMove"@touchend="handleTouchEnd"style="height: 100rpx"><view:style="{color: currentIndex === index ? 'yellow' : '#FFFFFF',transform: `translateX(${currentIndex === index ? '20rpx' : '0'})`,}"class="top_item"v-for="(item, index) in typeList":key="index">{{ item?.name }}</view></view><view class="bottom"><image@tap="handlePreviewImage(photoPath, [photoPath])"v-if="photoPath"class="pic":src="photoPath"/><view v-else class="pic">暂无</view><view><uni-icons@click="handleClick":color="isRecord ? '#ff0000' : `#ffffff`"type="circle-filled"size="56"></uni-icons></view><view><uni-icons@click="handleLoop"type="loop"color="#ffffff"size="40"></uni-icons></view></view></view></view>
</template><script setup lang="ts">
import { reactive, ref, onMounted, watch } from "vue";
import { handlePreviewImage } from "@/utils/common";// 相机实例
const cameraRef = ref(null);
// 照片路径
const photoPath = ref("");
//摄像头类型
const cameraType = ref<"back" | "front">("back");
//记录loop切换的类型
const loopFlag = ref(false);
//记录是否开始录制
const isRecord = ref(false);// 缩放相关
const initialDistance = ref(0); // 初始双指距离
const currentZoom = ref(1); // 当前缩放级别(初始1)
const maxZoom = 2.5; // 最大缩放级别
const minZoom = 1; // 最小缩放级别//操作类型
const typeList = reactive([{name: "拍照",type: 1,},{name: "录像",type: 2,},
]);const currentIndex = ref(0);
const startX = ref(1);
const endX = ref(1);// 双指缩放逻辑
const handleZoomStart = (e: TouchEvent) => {if (e.touches.length >= 2) {initialDistance.value = Math.hypot(e.touches[0].clientX - e.touches[1].clientX,e.touches[0].clientY - e.touches[1].clientY);}
};const handleZoomMove = (e: TouchEvent) => {if (e.touches.length >= 2 && initialDistance.value > 0) {const currentDistance = Math.hypot(e.touches[0].clientX - e.touches[1].clientX,e.touches[0].clientY - e.touches[1].clientY);// 计算缩放变化(更平滑的算法)const zoomDelta = (currentDistance - initialDistance.value) / 200;let newZoom = currentZoom.value + zoomDelta;newZoom = Math.max(minZoom, Math.min(maxZoom, newZoom));if (newZoom !== currentZoom.value) {currentZoom.value = newZoom;setCameraZoom(newZoom);}initialDistance.value = currentDistance;}
};// 设置相机缩放
const setCameraZoom = (zoom: number) => {const cameraContext = uni.createCameraContext();cameraContext.setZoom({zoom: zoom,success: () => console.log("缩放设置成功:", zoom),fail: (err) => console.error("缩放失败:", err),});
};// 初始化时设置默认缩放
onMounted(() => {setCameraZoom(1); // 初始化为1x
});// 其他原有方法保持不变(handleTouchStart、handleClick等...)
function handleTouchStart(e: any) {console.log(e, "start");startX.value = e.touches[0].clientX;
}function handleTouchMove(e: any) {endX.value = e.touches[0].clientX;
}function handleTouchEnd() {const diffX = startX.value - endX.value;if (diffX > 50 && currentIndex.value < typeList.length - 1) {currentIndex.value++;} else if (diffX < -50 && currentIndex.value > 0) {currentIndex.value--;}startX.value = endX.value = 0;
}const handleLoop = () => {loopFlag.value = !loopFlag.value;cameraType.value = loopFlag.value ? "front" : "back";
};const handleClick = () => {if (currentIndex.value === 0) {takePhoto();} else {isRecord.value ? stopRecord() : startRecord();}
};const takePhoto = async () => {try {const cameraContext = uni.createCameraContext();cameraContext.takePhoto({quality: "high",success: (res) => {photoPath.value = res.tempImagePath;},fail: console.error,});} catch (e) {console.error("相机异常", e);}
};const startRecord = () => {uni.showToast({ title: "开始录像", duration: 500 });const cameraContext = uni.createCameraContext();cameraContext.startRecord({timeout: 60000,success: () => (isRecord.value = true),fail: (err) => {isRecord.value = false;console.error("开始录像失败", err);},});
};const stopRecord = () => {isRecord.value = false;const cameraContext = uni.createCameraContext();cameraContext.stopRecord({success: (res) => {uni.showToast({ title: "录像结束", duration: 500 });photoPath.value = res?.tempThumbPath;},fail: console.error,});
};const handleCameraError = (e: any) => {console.error("相机错误", e);
};//如果切换拍照、录像,处于录像状态,则停止录像
watch(currentIndex, (val) => {if (isRecord.value) {stopRecord();}
});
</script><style scoped lang="scss">
.container {display: flex;flex-direction: column;align-items: center;background: black;color: white;width: 100vw;height: 100vh;position: relative;
}.zoom-indicator {position: absolute;top: 20rpx;left: 20rpx;background: rgba(0, 0, 0, 0.5);color: white;padding: 10rpx 20rpx;border-radius: 20rpx;z-index: 10;
}.btn_group {flex: 1;width: 100vw;display: flex;flex-direction: column;box-sizing: border-box;.top {width: 100%;display: flex;align-items: center;justify-content: center;.top_item {width: auto;color: white;margin-right: 32rpx;transition: all 0.3s ease;}}.bottom {margin-top: 20rpx;flex: 1;box-sizing: border-box;padding: 0 60rpx;display: flex;align-items: center;justify-content: space-between;.pic {width: 70rpx;height: 70rpx;border-radius: 12rpx;background: white;color: black;font-size: 20rpx;line-height: 70rpx;text-align: center;}}
}
</style>

希望本文对您实现相机功能有所帮助。注意:没有加录制超时逻辑,需要的话自行在startRecord回调中添加!

http://www.xdnf.cn/news/1378621.html

相关文章:

  • ARM相关的基础概念和寄存器
  • PCIe 5.0 SSD连续读写缓存用完速度会骤降吗?
  • 2024年09月 Python(三级)真题解析#中国电子学会#全国青少年软件编程等级考试
  • 帕累托优化:多目标决策的智慧与艺术
  • 【重学 MySQL】九十二、 MySQL8 密码强度评估与配置指南
  • 关于virtual camera
  • 【C++游记】模板升级
  • 【半导体制造流程概述】
  • windows 子系统 wsl 命令的用法
  • vue3 字符 居中显示
  • SpringBoot整合Redis:从入门到实战的完整指南
  • 关于DTO、DO、BO、VO
  • 工业 DCS 全面科普:从入门到 AI 赋能的未来
  • mybatis-plus实现苍穹外卖项目-分类操作,不定期更新-day2
  • 【和春笋一起学C++】(三十七)类的析构函数
  • 死锁产生的条件是什么? 如何进行死锁诊断?
  • leetcode 974 和可被K整除的子数组
  • 集成电路学习:什么是YOLO一次性检测器
  • 关于国产 RAC 和分布式研讨
  • 【Python学习笔记】whl包打包
  • Day14——JavaScript 核心知识全解析:变量、类型与操作符深度探秘
  • Redis实战-优惠券秒杀解决方案总结大全
  • XC6SLX75-2FGG484C Xilinx Spartan-6 LX FPGA
  • 电子电气架构 --- 软件项目复杂性的驾驭思路
  • 基于Prometheus Pushgateway与Alertmanager的自定义指标监控与告警实践指南
  • C语言 | 高级C语言面试题
  • C语言二级考试环境配置教程【window篇】
  • 数学建模——马尔科夫链(Markov Chain Model)
  • Linux初始——基础指令篇
  • 数据结构:从堆中删除元素 (Deleting from a Heap)