浏览器拍照功能(Vue+ElementUI组件)

最近项目上比较忙额没有时间写些东西分享出来,今天忙里偷闲决定把我封装的拍照组件分享出来。如果有兴趣的话看我之前写的文章应该能看到我有说过如何通过API来获取媒体设备,这次就将完整代码跟大家分享一下,代码我会贴在最后面。

一、实现原理

这个组件是web端的组件,使用的也是webAPI,我们最主要的API是navigator,这个API给了我们调用媒体设备的能力。在我们通过API获取到媒体数据后我们可以通过转换的形式将得到的流数据用video展示出来,具体操作可以看我之前的文章。接下来我们会用到画布,画布的强大我自不必多说,主要还是通过画布的drawImage方法来获取video标签的某一帧图片。

二、代码

1.html部分:
<template>
  <el-dialog :title="title" :visible.sync="photoVisible" width="60%" :before-close="handleClose" custom-class="dialog-double">
    <div id="photo-wrapper">
      <div class="btn-group">
        <el-button class="changeButton" @click="getCompetence" type="primary">开启摄像头</el-button>
        <el-button class="changeButton" @click="closeCamera" :disabled="closeAble && mediaStreamTrack">关闭摄像头</el-button>
        <el-button class="changeButton" id="btn" type="primary">拍照</el-button>
        <el-button class="changeButton" id="btn1" type="primary" @click="submit">提交图片</el-button>
      </div>
      <el-select v-if="devicesList.length > 1" v-model="currentDeviceID" clearable placeholder="请选择设备">
        <el-option v-for="device in devicesList" :key="device.deviceId" :label="device.label" :value="device.deviceId">
        </el-option>
      </el-select>
      <div class="show-box">
        <video src="" id="v1" :style="showStyle"></video>
        <canvas id="c1" width="800" height="450" :style="{ display: 'none' }"></canvas>
        <img id="img" src="" :style="showStyle" />
      </div>
    </div>
  </el-dialog>
</template>
2.script部分
export default {
  name: "BrowserPhoto",
  props: {
    photoVisible: {
      type: Boolean,
      default: false,
    },
    title: {
      type: String,
      default: "",
    },
    showStyle: {
      type: Object,
      default: () => ({
        width: "400px",
      }),
    },
    videoConfig: {
      type: Object,
      default: () => ({
        width: 1280, //1280
        height: 720, //720
      }),
    },
    // 是否在没有摄像头开启时禁用关闭按钮
    closeAble: {
      type: Boolean,
      default: false,
    },
    url: {
      type: String,
    },
  },
  data() {
    return {
      currentDeviceID: "", //当前媒体设备ID
      mediaStreamTrack: "", //媒体设备对象
      devicesList: [], //设备列表
      imgFile: "", //图片文件
    };
  },
  watch: {
    currentDeviceID(newV, oldV) {
      console.log(newV);
      if (newV !== oldV) {
        this.closeCamera();
      }
    },
  },
  methods: {
    searchDevice() {
      if (window.navigator.mediaDevices && window.navigator.mediaDevices.getUserMedia) {
      }
      navigator.mediaDevices.enumerateDevices().then((devices) => {
        let videoList = devices.filter((item) => {
          return item.kind == "videoinput";
        });
        this.devicesList = videoList;
        if (videoList.length == 0) {
          this.$message({
            message: "没有查询到视频输入设备!",
            type: "error",
          });
          return false;
        } else if (videoList.length == 1) {
          this.currentDeviceID = videoList[0].deviceId;
        } else if (videoList.length > 1) {
          this.$message({
            message: "当前设备有多个视频输入设备,可根据需要切换视频输入设备",
            type: "info",
          });
        }
      });
    },
    getCompetence() {
      let video = document.querySelector("#v1");
      let photo = document.querySelector("#c1");
      let ctx = photo.getContext("2d");
      let btn = document.querySelector("#btn");
      let btn1 = document.querySelector("#btn1");
      let img = document.querySelector("#img");
      let download = document.querySelector("#download");
      const h = this.$createElement;
      navigator.mediaDevices
        .getUserMedia({
          video: {
            deviceId: this.currentDeviceID || this.devicesList[0],
            ...this.videoConfig,
          },
          audio: false,
        })
        .then((stream) => {
          let mediaStreamTrack = stream.getTracks()[0];
          this.mediaStreamTrack = mediaStreamTrack;
          video.srcObject = stream;
          video.play();
          btn.addEventListener(
            "click",
            () => {
              let devicePixelRatio = window.devicePixelRatio || 1;
              let backingStoreRatio =
                ctx.webkitBackingStorePixelRatio ||
                ctx.mozBackingStorePixelRatio ||
                ctx.msBackingStorePixelRatio ||
                ctx.oBackingStorePixelRatio ||
                ctx.backingStorePixelRatio ||
                1;
              let ratio = devicePixelRatio / backingStoreRatio;
              console.log(ratio);
              photo.width = photo.width * ratio;
              photo.height = photo.height * ratio;
              ctx.drawImage(
                video,
                0,
                0,
                800 * ratio,
                ((800 * this.videoConfig.height) / this.videoConfig.width) * ratio
              );
              ctx.scale(ratio, ratio);

              // canvas.toDataURL 返回的是一串Base64编码的URL
              img.src = photo.toDataURL("image/png");
              let arr = photo.toDataURL("image/png").split(",");
              let type = arr[0].match(/:(.*?);/)[1];
              let bstr = atob(arr[1]);
              let n = bstr.length;
              let u8arr = new Uint8Array(n);
              while (n--) {
                u8arr[n] = bstr.charCodeAt(n);
              }
              this.imgFile = new File([u8arr], "img" + new Date().getTime(), { type });
              console.log(this.imgFile);
              this.$emit("upload", this.imgFile);
              // 恢复宽高
              photo.width = photo.width / ratio;
              photo.height = photo.height / ratio;
            },
            false
          );
        })
        .catch((err) => {
          console.log(err);
        });
    },
    closeCamera() {
      console.log(this.mediaStreamTrack);
      if (this.mediaStreamTrack) {
        this.mediaStreamTrack.stop();
      }
      // else {
      //   this.$message({
      //     message: "关闭失败,请检查当前设备是否开启摄像头",
      //     type: "error",
      //   });
      // }
    },
    handleClose() {
      this.$emit("update:photoVisible", false);
    },
    submit() {
      let url = this.url;
      let param = new FormData();
      param.append("file", this.imgFile);
      let config = {
        headers: { "Content-Type": "multipart/form-data" },
      };
      http.post(url, param, config).then((res) => {
        console.log(res);
        this.$emit("uploadSuccess", res);
      });
    },
  },
  mounted() {
    this.searchDevice();
    // if(this.closeAble){
    // }
  },
  beforeDestroy() {
    this.closeCamera();
  },
};

三、注意事项

其实没啥好讲的,东西都在代码里,看就完事了,重点在于canvas生成的图片的模糊度问题,这一点除了和ratio有关外还和生成的图片页面比例有关,慢慢调就完事了。希望对大家有所帮助,开源互助。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值