vue3+h5实现自定义拍照功能

在手机端,调用摄像头需要在 HTTPS 或 localhost 下访问,还需要用户事先进行授权

效果图:
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/72d14669688c443b840031284b03b4cb.png
在这里插入图片描述
takePicture.vue(自定义拍照组件)

<template>
    <div ref="wrapperRefRef">
      <div class="take-picture" v-if="imgUrl">
        <img :src="imgUrl" alt="">
      </div>
      <video ref="videoRef" class="take-picture" v-else></video>
      
      <div class="wrapper_btm" v-if="imgUrl">
        <span class="open_album" @click="closePicture">取消</span>
        <van-button type="primary" @click="refreshPicture" class="take-picture-btn">返回</van-button>
        <span class="close_picture" @click="submitResult">确定</span>
      </div>
      <div class="wrapper_btm" v-else>
        <input type="file" accept="image/*" ref="galleryRef" @change="handleFileChange" style="display: none;"/>
        <span class="open_album" @click="openGallery">相册</span>
        <van-button type="primary" @click="handleShoot" class="take-picture-btn">拍照</van-button>
        <span class="close_picture" @click="closePicture">关闭</span>
        
      </div>
    </div>
</template>
<script lang="ts" setup>

import { ref, onMounted, defineEmits, defineProps, watch } from "vue";
import { showToast, showNotify} from 'vant';
import { compressImage } from '@/utils/compressImage';

// import { emit } from "process";
const emit = defineEmits(["closePicture", "pictureResult"])
const videoRef = ref<HTMLVideoElement | null>(null);
const imgUrl = ref('');
const imgResult = ref<File | null>(null);

const props = defineProps({  
  show: {  
    type: Boolean,  
    required: true,
    default: false,
  },
});

// 获取媒体流,检测是否支持getUserMedia拍照
const getMediaStream = async () => {
  try {
      const stream = await navigator.mediaDevices.getUserMedia({
        video: { 
            facingMode: { exact: 'environment' } 
        },
        audio: false,
      });
      if (videoRef.value) {
        videoRef.value.srcObject = stream;
        videoRef.value.play();
      }
  } catch (error) {
      console.error('无法获取媒体流', error);
      showNotify({ type: 'danger', message: '无法获取媒体流' });
  }
};
// 点击拍照
const handleShoot = () =>  {
  if (!videoRef.value) return;
  // 设置canvas画布  
  const canvas = document.createElement("canvas");
  canvas.width = videoRef.value.videoWidth;
  canvas.height = videoRef.value.videoHeight;
  // 获取canvas上下文对象
  const ctx = canvas.getContext("2d");
  // 截图操作
  ctx?.drawImage(videoRef.value, 0, 0, canvas.width, canvas.height);
  // 转为文件流
  const img = canvas.toDataURL("image/jpeg");
  canvas.toBlob((blob) => {
      if (blob) {
          const newFile = new File([blob], 'takePicture.jpg', { type: 'image/jpeg' });
          imgResult.value = newFile;
      }
  }, 'image/jpeg', 1); // 0.8 为压缩质量
  imgUrl.value = img;
}
const galleryRef = ref<HTMLInputElement | null>(null);
// 打开相册
const openGallery = () => {
  galleryRef.value?.click();
}
// 相册文件选择
const handleFileChange = (e: any) => {
    // 调用压缩图片方法
    compressImage(e.target.files[0], (file) => {
        emit("pictureResult", file);
        // 关闭相机
        closePicture();
    });
};
// 重置相机
const refreshPicture = () => {
  if (videoRef.value) {
    videoRef.value.srcObject = null;
  }
  imgUrl.value = '';
  getMediaStream();
}
// 关闭相机
const closePicture = () => {
  if (videoRef.value) {
    videoRef.value.srcObject = null;
  }
  imgUrl.value = '';
  emit("closePicture", false);
}
// 提交拍照
const submitResult = () => {
  emit("pictureResult", imgResult.value);
  closePicture();
}

onMounted(() => {
  getMediaStream();
  watch(() => props.show, (newValue, oldValue) => {
    console.log('newValue', newValue)
    if (newValue) {
      getMediaStream();
    }
  });
});
</script>
<style scoped>
.take-picture{
  width: 100%;
  height: 80vh;
  position: absolute;
  top: 0;
  left: 0;
  /* background: rgba(255,255,255,.5); */
}
.wrapper_btm{
  display: flex;
  width: 100%;
  height: 20vh;
  background: rgb(10, 10, 10);
  flex-wrap: wrap;
  align-content: center;
  justify-content: center;
  /* align-items: center; */
  position: absolute;
  bottom: 0;
  left: 0;
}
.take-picture-btn{
  width: 4rem !important;
  height: 4rem !important;
  border-radius: 50% !important;
  background: #fff !important;
  border: none !important;
  color: #000;
}
.open_album{
  font-size: 1rem;
  position: absolute;
  top: 50%;
  left: 25%;
  /* left: 50%; */
  transform: translateY(-50%);
  color: #fff;
}
.close_picture{
  font-size: 1rem;
  position: absolute;
  top: 50%;
  right: 25%;
  /* left: 50%; */
  transform: translateY(-50%);
  color: #fff;
}
</style>

使用拍照组件

<template>
<van-overlay :show="show" @click="show = false">
<takePicture @closePicture="show = false" :show="show" @pictureResult="handlePictureResult" />
</van-overlay>
</template>
<script lang="ts" setup>
import { ref, reactive, defineEmits, defineProps, getCurrentInstance  } from 'vue';
const show = ref(false);
// 使用takePicture组件的拍照返回结果
const handlePictureResult = (result: File) => {
    console.log('拍照返回结果', result);
    // 上传图片
    const img1 = document.createElement('img');
    img1.src = URL.createObjectURL(result);
    img1.style.width = '100%';
    img1.style.height = '100%';
    document.body.appendChild(img1);
}
</script>

compressImage.ts文件压缩

// 压缩图片质量
export const compressImage = (file: File, callback: (file: File) => void) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = (e) => {
        const img = new Image();
        img.src = e.target?.result as string;
        img.onload = () => {
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            const MAX_WIDTH = 800;
            const MAX_HEIGHT = 800;
            let width = img.width;
            let height = img.height;
            if (width > height) {
                if (width > MAX_WIDTH) {
                    height *= MAX_WIDTH / width;
                    width = MAX_WIDTH;
                }
            } else {
                if (height > MAX_HEIGHT) {
                    width *= MAX_HEIGHT / height;
                    height = MAX_HEIGHT;
                }
            }
            canvas.width = width;
            canvas.height = height;
            ctx?.drawImage(img, 0, 0, width, height);

            // const newFile = new File([canvas.toDataURL('image/jpeg', 0.8).split(',')[1]], file.name, { type: 'image/jpeg' });
            // callback(newFile);
            canvas.toBlob((blob) => {
                if (blob) {
                    const newFile = new File([blob], file.name, { type: 'image/jpeg' });
                    callback(newFile);
                }
            }, 'image/jpeg', 0.9); // 0.8 为压缩质量
        };
    };
};
Vue3 中,你可以使用 HTML5 的 `getUserMedia` API 和 `FileReader` 对象来实现H5 页面上打开摄像头并定期拍照功能。下面是一个简单的步骤概述: 1. 引入必要的依赖:确保你在项目中安装了 Vue,并可能需要引入一些库来处理文件流和图片上传,比如 `vue-file-preview` 或者 `axios`。 ```html <template> <div id="camera-preview"> <!-- ... --> </div> </template> <script setup> import { ref, onMounted } from &#39;vue&#39;; import { useCamera, useCanvasToBlob } from &#39;vue-file-preview&#39;; // 使用合适的库 const camera = useCamera(); let captureInterval; // 定义定时器变量 onMounted(async () => { try { // 请求访问用户摄像头权限 await navigator.mediaDevices.getUserMedia({ video: true }); // 开始视频流 camera.play(); // 设置定时拍照功能 captureInterval = setInterval(() => { if (camera.stream) { // 拍照并将结果转换为blob const blob = await useCanvasToBlob(camera.canvas); // 可以选择存储、发送或显示图片 handleSaveImage(blob); // 自定义保存或上传图片的函数 } }, 5000); // 每隔5秒拍照一次 } catch (error) { console.error(&#39;Failed to access camera:&#39;, error); } }); function handleSaveImage(blob) { // 这里可以使用axios或其他方式将blob上传到服务器,或者显示在前端页面上 // axios.post(&#39;/api/save-image&#39;, { file: blob }); } </script> ``` 记得替换 `useCamera` 和 `useCanvasToBlob` 为你使用的库提供的相应函数,并根据需求调整间隔时间(例如,5000毫秒等于5秒)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值