实现微信小程序的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回调中添加!