前端调用摄像头拍照生成图片加裁切

<template>
  <div>
    <div style="display: flex; align-items: center">
      <el-button type="primary" @click="callCamera">点击打开摄像头</el-button>
      <el-button size="mini" type="primary" @click="photograph">拍照</el-button>
      <el-button size="mini" type="primary" @click="upDataPhotoApi">
        上传照片
      </el-button>
    </div>
    <br />
    <div style="display: flex">
      <div style="margin-right: 20px; border: 1px solid #ccc">
        <!--图片展示-->
        <video ref="video" autoplay height="480" width="640"></video>
        <!--确认-->
      </div>
      <!--canvas截取流-->
      <canvas v-show="false" id="canvas" ref="canvas" height="480" width="640"></canvas>
      <Crop v-if="headImgSrc" ref="cROPLL" :image-src="headImgSrc" @updateimagesrc="updateImageSrc" />
    </div>
    <br />
  </div>
</template>

<script>
//接口和base64接口
import { upDataPhoto,uploadBase64Images } from '@/api/village/DoorCheckInLogManagement'
//裁切
import Crop from './crop'
export default defineComponent({
  name: 'PhotoComponents',
  components: {
    Crop,
  },
  // props: ["data", "url"],
  props: {
    type: {
      type: Number,
      default: '',
    },
    data: {
      type: String,
      default: '',
    },
    url: {
      type: String,
      default: '',
    },
  },
  emits: ['getbyid',"getphonedata"],
  setup(props, { emit }) {
    const state = reactive({
      cROPLL: null,
      headImgSrc: '',
      video: null,
      canvas: null,
    })
    const $baseMessage = inject('$baseMessage')

    // 调用摄像头
    const callCamera = () => {
      // H5调用电脑摄像头API
      //navigator.mediaDevices.getUserMedia 调用后 会提示用户给予使用媒体输入的许可.返回一个 Promise 对象,
      //成功后会resolve回调一个 MediaStream 对象。
      //在移动设备上面调用摄像头也可以通过此种方式,如下的例子表示优先使用前置摄像头
      //{ audio: true, video: { facingMode: "user" } }前摄像头
      //{ audio: true, video: { facingMode: { exact: "environment" } } }强制使用后置摄像头
      //设置获取接近 1280x720 的相机分辨率
      //{ audio: true, video: { width: 1280, height: 720 } };
      navigator.mediaDevices
        .getUserMedia({
          // audio: true,//调用音频
          video: true, //说明请求的媒体类型。调用视频
        })
        .then((success) => {
          // 摄像头开启成功
          state.video.srcObject = success //src 是播多媒体文件的;srcobj 是实时流
          // 实时拍照效果
          state.video.play()
        })
        .catch(() => {
          //用户拒绝
          console.error('摄像头开启失败,请检查摄像头是否可用!')
        })
    }

    const photograph = () => {
      const ctx = state.canvas.getContext('2d')
      // 把当前视频帧内容渲染到canvas上
      ctx.drawImage(state.video, 0, 0, 640, 480)
      //canvas图片 转base64格式、图片格式转换、图片质量压缩
      //语法 canvas.toDataURL(type, encoderOptions); 设置转换的图片格式、图片质量压缩
      const imgBase64 = state.canvas.toDataURL('image/jpeg', 1)

      // let str = imgBase64.replace("data:image/jpeg;base64,", "");
      // let strLength = str.length;
      // let fileLength = parseInt(strLength - (strLength / 8) * 2); //
      // let size = (fileLength / 1024).toFixed(2);// 由字节转换为KB 判断大小
      // console.log(size);
      function useImg(data) {
        state.headImgSrc = ''
        setTimeout(() => {
          state.headImgSrc = data
        })
      }
      dealImage(imgBase64, 640, useImg)
    }

    const dealImage = (base64, w, callback) => {
      const newImage = new Image()
      let quality = 0.6 //压缩系数0-1之间
      newImage.src = base64
      newImage.setAttribute('crossOrigin', 'Anonymous') //url为外域时需要
      let imgWidth, imgHeight
      newImage.onload = function () {
        imgWidth = 640
        imgHeight = 480
        const canvas = document.createElement('canvas')
        const ctx = canvas.getContext('2d')
        if (Math.max(imgWidth, imgHeight) > w) {
          if (imgWidth > imgHeight) {
            canvas.width = w
            canvas.height = (w * imgHeight) / imgWidth
          } else {
            canvas.height = w
            canvas.width = (w * imgWidth) / imgHeight
          }
        } else {
          canvas.width = imgWidth
          canvas.height = imgHeight
          quality = 0.6
        }
        ctx.clearRect(0, 0, canvas.width, canvas.height)
        ctx.drawImage(this, 0, 0, canvas.width, canvas.height)
        const base64 = canvas.toDataURL('image/jpeg', quality) //压缩语句
        // 如想确保图片压缩到自己想要的尺寸,如要求在100-200kb之间,请加以下语句,quality初始值根据情况自定
        // while (base64.length / 1024 > 200) {
        // 	quality -= 0.01;
        // 	base64 = canvas.toDataURL("image/jpeg", quality);
        // }
        // // 防止最后一次压缩低于最低尺寸,只要quality递减合理,无需考虑
        // while (base64.length / 1024 < 100) {
        // 	quality += 0.001;
        // 	base64 = canvas.toDataURL("image/jpeg", quality);
        // }
        callback(base64) //必须通过回调函数返回,否则无法及时拿到该值,return拿不到
      }
    }
    // 上传照片
    const upDataPhotoApi = async () => {
      if (state.headImgSrc) {
        if (!props.type) {
                 //接口生成图片
          const { msg } = await upDataPhoto({
            villageId: props.data.villageId,
            id: props.data.personId,
            doorId: props.data.doorId,
            picBase64Str: state.headImgSrc,
          })
          $baseMessage(msg, 'success', 'vab-hey-message-success')
          state.headImgSrc = ''
          emit('getbyid')
          const c = document.getElementById('canvas')
          const h = c.height
          c.height = h
          closeCamera()
        }else if(props.type == 1){
          const { msg,data } = await uploadBase64Images({
            base64Img: state.headImgSrc,
          })
          $baseMessage(msg, 'success', 'vab-hey-message-success')
          emit('getphonedata',data)
          state.headImgSrc = ''
          const c = document.getElementById('canvas')
          const h = c.height
          c.height = h
          closeCamera()
        }

      } else {
        $baseMessage('请先拍照', 'error', 'vab-hey-message-error')
      }
    }

    // 关闭摄像头
    const closeCamera = () => {
      if (!state.video.srcObject) {
        return
      }
      //MediaStream 接口的getTracks() 方法会返回一个包含媒体流中所有媒体对象
      const stream = state.video.srcObject
      const tracks = stream.getTracks()
      tracks.forEach((track) => {
        track.stop()
      })
      state.video.srcObject = null
    }
    const updateImageSrc = (data) => {
      console.log(data, '-----------')
      state.headImgSrc = data
    }
    onMounted(() => {
      console.log(props)
    })

    return {
      ...toRefs(state),
      photograph,
      callCamera,
      closeCamera,
      upDataPhotoApi,
      dealImage,
      updateImageSrc,
    }
  },
})
</script>

crop.vue

//cropperImg.vue
<template>
  <div>
    <!--使用ref属性给图片元素命名为imageRef-->
    <el-button size="mini" type="primary" @click="cropImage">
      裁剪图片
    </el-button>
    <img ref="imageRef" :src="imageSrc" style="width: 640px; height: 480px" />
  </div>
</template>

<script setup>
  import {
    ref,
    onMounted,
    onBeforeUnmount,
    defineProps,
    defineEmits,
  } from 'vue'
  import Cropper from 'cropperjs'
  import 'cropperjs/dist/cropper.css'
  const props = defineProps({
    //图片地址
    imageSrc: {
      type: String,
      required: true,
    },
    //aspectRatio:置裁剪框为固定的宽高比
    aspectRatio: {
      type: Number,
      default: 0.6,
    },
    //viewMode: 视图控制
    viewMode: {
      type: Number,
      default: 0.1,
    },
    //autoCropArea: 设置裁剪区域占图片的大小 值为 0-1 默认 0.8 表示 80%的区域
    autoCropArea: {
      type: Number,
      default: 0.1,
    },
  })
  //绑定图片的dom对象
  const imageRef = ref(null)
  let cropper = null
  //使用Cropper构造函数创建裁剪器实例,并将图片元素和一些裁剪选项传入
  onMounted(() => {
    cropper = new Cropper(imageRef.value, {
      aspectRatio: props.aspectRatio,
      viewMode: props.viewMode,
      autoCropArea: props.autoCropArea,
    })
  })

  //定义方法
  const emit = defineEmits(['updateimagesrc'])
  //在cropImage函数中,获取裁剪后的图片数据URL,并使用emit方法触发updateImageSrc事件,
  // 将裁剪后的图片数据URL传递给父组件。
  const cropImage = () => {
    const canvas = cropper.getCroppedCanvas()
    const croppedImage = canvas.toDataURL()
    emit('updateimagesrc', croppedImage)
  }
  const create = () => {
    cropper.destroy()
  }
  //销毁
  onBeforeUnmount(() => {
    create()
  })
</script>

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值