人脸登陆——Vue2 调用浏览器摄像头拍照及图片上传组件

目录

1、需求背景

2、实现步骤

1)开发环境开启https

2)摄像头初始化(默认优先前置摄像头)

3)打开,切换,关闭摄像头

4)拍照

5)完整源码

3、应用效果


1、需求背景

当前vue图片组件有上传图片组件,截图组件,预览图片组件,但是没有从电脑摄像头或手机摄像头获取现场照片的能力,为了实现人脸注册和人脸登陆,这篇博文就来实现这个功能。

实现网页调用计算机摄像头,需要使用https协议

2、实现步骤

1)开发环境开启https

在vue-config.js文件中找到devServer配置,新增 https:true选项即可以开启。

devServer: {
    host: '0.0.0.0',
    port: port,
    open: true,
    https: true,
    proxy: {
      '/admin-api/': { // 匹配以//admin-api开头的才生效 
        target: "http://192.168.1.222:48080",  // 实际请求主机,端口,协议
        changeOrigin: true,                 // 如果接口跨域这里就要这个参数配置
        secure:false,                       // 如果实际请求地址是https接口,则设置为true
        pathRewrite: {
          '^/admin-api/': '/admin-api/'     // 将本地 ~/admin-api/ping 请求转发为请求 http://192.168.1.222:48080/admin-api/ping
        }               
      },

    },
    disableHostCheck: true
  },

2)摄像头初始化(默认优先前置摄像头)

要调用网页摄像头,除了开启https服务,还需要对浏览器进行适配,不同的浏览器获取UserMedia方法略有不同,initUserMedia方法实现初始化工作,代码如下:

initUserMedia(){
      // 旧版本浏览器可能根本不支持mediaDevices,我们首先设置一个空对象
      if (navigator.mediaDevices === undefined) {
        navigator.mediaDevices = {}
      }
      // 一些浏览器实现了部分mediaDevices,我们不能只分配一个对象
      // 使用getUserMedia,因为它会覆盖现有的属性。
      // 这里,如果缺少getUserMedia属性,就添加它。
      if (navigator.mediaDevices.getUserMedia === undefined) {
        navigator.mediaDevices.getUserMedia = function (constraints) {
          // 首先获取现存的getUserMedia(如果存在)
          var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.getUserMedia
          // 有些浏览器不支持,会返回错误信息
          // 保持接口一致
          if (!getUserMedia) {
            return Promise.reject(new Error('getUserMedia is not implemented in this browser'))
          }
          // 否则,使用Promise将调用包装到旧的navigator.getUserMedia
          return new Promise(function (resolve, reject) {
            getUserMedia.call(navigator, constraints, resolve, reject)
          })
        }
      }
    },

摄像头初始化参数参考,前置摄像头facingMode: "user",

     constraints: { 
        audio: false,                                 // 是否打开麦克风
        video: { 
          width: 400,                                 
          height: 400, 
          frameRate: { ideal: 24, max: 24 },          // 视频帧频,不同设备的性能不一样,不宜太高
          transform: 'scaleX(-1)',
          facingMode: "user",                         // 优先打开前置摄像头
        } 
      },

3)打开,切换,关闭摄像头

// 调用权限(打开摄像头功能)
    async openCamera() {
      this.closeCamera();
      this.openVideo = true;
      this.loadingVideo = true;
      this.video = this.$refs.video
      this.initUserMedia()
      if(!this.constraints.video.deviceId){
        var devices = await navigator.mediaDevices.enumerateDevices()
        this.videoList = devices.filter(d=>d.kind=='videoinput');
        if(this.videoList.length<1) {
          this.$modal.msgError("没有检测到视频设备")
          return;
        }
        // console.log(this.videoList);
        var deviceId = this.videoList[0].deviceId;
        if(deviceId) {
          this.deviceId = deviceId;
          this.constraints.video.deviceId={ exact: deviceId}
        }
      }
      
      navigator.mediaDevices.getUserMedia(this.constraints).then((stream) =>{
       
        this.stream = stream
        // 旧的浏览器可能没有srcObject
        if ('srcObject' in this.video) {
          //  console.log("srcObject")
          this.video.srcObject = this.stream 
          this.$emit("ready", this.stream)
        } else {
          // 避免在新的浏览器中使用它,因为它正在被弃用。
          this.video.src = window.URL.createObjectURL(this.stream )
        }
        this.video.onloadedmetadata = (e)=> {
          this.video.play();
          this.loadingVideo = false;
        }
        this.$emit("ready", this.stream)

      }).catch(err => {
        console.log(err)
        this.openVideo = false;
          this.loadingVideo = false;
      })
    },

其中,这一段实现多摄像头探测

     if(!this.constraints.video.deviceId){
        var devices = await navigator.mediaDevices.enumerateDevices()
        this.videoList = devices.filter(d=>d.kind=='videoinput');
        if(this.videoList.length<1) {
          this.$modal.msgError("没有检测到视频设备")
          return;
        }
        // console.log(this.videoList);
        var deviceId = this.videoList[0].deviceId;
        if(deviceId) {
          this.deviceId = deviceId;
          this.constraints.video.deviceId={ exact: deviceId}
        }
      }

摄像头切换

   changeVideo(index){
      var deviceId = this.videoList[index].deviceId;
      if(this.deviceId == deviceId) return;
      this.deviceId = deviceId;
      this.closeCamera();
      this.constraints.video.deviceId={ exact: deviceId}
      this.openCamera();
    },

关闭摄像头

// 关闭摄像头
    closeCamera() {
      if(this.video && this.video.srcObject){
        this.video.srcObject.getTracks().forEach(track => track.stop());
        this.video.srcObject = null
      }
      this.stream = undefined;
    },

4)拍照

    //  绘制图片(拍照功能)
    takePhoto() {
      // console.log("takePhoto")
      var canvas = this.$refs.canvas
      var canvasContext = canvas.getContext('2d')
      // 点击,canvas画图
      if(this.video){
        canvasContext.drawImage(this.video, 0, 0, this.width, this.height)
        // 获取图片base64链接
        this.image = canvas.toDataURL('image/png')
        this.$emit("capture", this.image)
      }
    },

5)图片上传及完整源码

新增src\components\VideoRecorder\VideoBox.vue文件

<template>
  <div class="border1 border-round-5" :style="{width:width+'px', height:height+'px'}" style="position:relative;">
    <canvas ref="canvas" style="display:none;" :width="width" :height="height"></canvas>
    <div v-show="openVideo" v-loading="loadingVideo" class="flex-center">
      <video ref="video" :width="width" :height="height" autoplay style=""></video>
    </div>
    <div v-if="videoList.length>1" style="position:absolute; top:10px; left:10px;">
      <span class="margin-r-10 border-round-5 bg-white inline-block color-grey" style="line-height: 30px; width:30px; height:30px; text-align:center;vertical-align:middle;"
       :style="{background: d.deviceId==deviceId?'#66aa6630':'#EEEEEE30'}"
      v-for="(d, index) in videoList" :key="d.deviceId" @click="changeVideo(index)">{{index+1}}</span>
    </div>
  </div>
</template>
<script>
import store from '@/store'
export default {
  name:"VideoBox",
  props: {
    value: String,
    width: {
      type:Number,
      default: 240
    },
    height:{
      type:Number,
      default: 240
    },
  },
  watch:{
    value: {
      handler(val) {
        this.image = val
      }
    },
  },
  data() {
    return {
      constraints: { 
        audio: false, 
        video: { 
          width: 400, 
          height: 400, 
          frameRate: { ideal: 24, max: 24 },
          transform: 'scaleX(-1)',
          // facingMode: "user",
        } 
      },
      deviceId: undefined,
      videoList: [],
      stream: null,
      // 拍照
      video:null,
      image: this.value,
      openVideo: false,
      loadingVideo: false,
      //
      mimeType:'video/webm',
      chunks:[],
    }
  },
  mounted() {
    this.openCamera();
  },
  destroyed() {
    this.closeCamera()
  },
  methods: {
    changeVideo(index){
      var deviceId = this.videoList[index].deviceId;
      if(this.deviceId == deviceId) return;
      this.deviceId = deviceId;
      this.closeCamera();
      this.constraints.video.deviceId={ exact: deviceId}
      this.openCamera();
    },
    removeImage(e){
      if(e && e.stopPropagation){
        e.stopPropagation();
      }
      this.image = null;
      this.$emit("input", this.image);
    },
    initUserMedia(){
      // 旧版本浏览器可能根本不支持mediaDevices,我们首先设置一个空对象
      if (navigator.mediaDevices === undefined) {
        navigator.mediaDevices = {}
      }
      // 一些浏览器实现了部分mediaDevices,我们不能只分配一个对象
      // 使用getUserMedia,因为它会覆盖现有的属性。
      // 这里,如果缺少getUserMedia属性,就添加它。
      if (navigator.mediaDevices.getUserMedia === undefined) {
        navigator.mediaDevices.getUserMedia = function (constraints) {
          // 首先获取现存的getUserMedia(如果存在)
          var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.getUserMedia
          // 有些浏览器不支持,会返回错误信息
          // 保持接口一致
          if (!getUserMedia) {
            return Promise.reject(new Error('getUserMedia is not implemented in this browser'))
          }
          // 否则,使用Promise将调用包装到旧的navigator.getUserMedia
          return new Promise(function (resolve, reject) {
            getUserMedia.call(navigator, constraints, resolve, reject)
          })
        }
      }
    },
    // 调用权限(打开摄像头功能)
    async openCamera() {
      this.closeCamera();
      this.openVideo = true;
      this.loadingVideo = true;
      this.video = this.$refs.video
      this.initUserMedia()
      if(!this.constraints.video.deviceId){
        var devices = await navigator.mediaDevices.enumerateDevices()
        this.videoList = devices.filter(d=>d.kind=='videoinput');
        if(this.videoList.length<1) {
          this.$modal.msgError("没有检测到视频设备")
          return;
        }
        // console.log(this.videoList);
        var deviceId = this.videoList[0].deviceId;
        if(deviceId) {
          this.deviceId = deviceId;
          this.constraints.video.deviceId={ exact: deviceId}
        }
      }
      
      navigator.mediaDevices.getUserMedia(this.constraints).then((stream) =>{
       
        this.stream = stream
        // 旧的浏览器可能没有srcObject
        if ('srcObject' in this.video) {
          //  console.log("srcObject")
          this.video.srcObject = this.stream 
          this.$emit("ready", this.stream)
        } else {
          // 避免在新的浏览器中使用它,因为它正在被弃用。
          this.video.src = window.URL.createObjectURL(this.stream )
        }
        this.video.onloadedmetadata = (e)=> {
          this.video.play();
          this.loadingVideo = false;
        }
        this.$emit("ready", this.stream)

      }).catch(err => {
        console.log(err)
        this.openVideo = false;
          this.loadingVideo = false;
      })
    },
    // 关闭摄像头
    closeCamera() {
      // console.log("closeCamera")
      if(this.video && this.video.srcObject){
        this.video.srcObject.getTracks().forEach(track => track.stop());
        this.video.srcObject = null
      }
      this.stream = undefined;
    },
    //  绘制图片(拍照功能)
    takePhoto() {
      // console.log("takePhoto")
      var canvas = this.$refs.canvas
      var canvasContext = canvas.getContext('2d')
      // 点击,canvas画图
      if(this.video){
        canvasContext.drawImage(this.video, 0, 0, this.width, this.height)
        // 获取图片base64链接
        this.image = canvas.toDataURL('image/png')
        this.$emit("capture", this.image)
      }
    },
    
  }
}

</script>

3、应用效果

新建src\views\system\user\profile\UserFaceList.vue 内容如下:

<template>
  <div>
    <div class="">
      <div>
        <el-image v-if="image" :src="image" style="width:300px;height:300px"/>
        <VideoBox v-show="!image" class="border1 border-round-5" ref="videoBox" :width="300" :height="300" @click="takePhoto"  @capture="handleCapture" ></VideoBox>
        <div class="flex-row-between padding-20" >
          <el-button type="primary" @click="takePhoto">{{image?'重拍':'拍照'}}</el-button>
          <el-button  class="margin-l-20" type="primary">注册</el-button>
          <div class="fill-rest"></div>
        </div>
      </div>
    </div>

  </div>
</template>

<script>
import { getDictDatas, DICT_TYPE } from '@/utils/dict'
import * as userFace from "@/api/system/userFace";
import VideoBox from '@/components/VideoRecorder/VideoBox';
export default {
  name: "UserFaceList",
  components:{
    VideoBox
  },
  data() {
    return {
      image: undefined,
      
    };
  },
  mounted(){
  },
  destroyed(){

  },
  methods: {
    startCamera(){
      if(this.$refs.videoBox){
        this.$refs.videoBox.openCamera();
      }
    },
    takePhoto(){
      if(this.image) {
        this.image = undefined;
        if(this.$refs.videoBox){
          this.$refs.videoBox.openCamera();
        }
      } else {
        if(this.$refs.videoBox){
          this.$refs.videoBox.takePhoto();
          this.$refs.videoBox.closeCamera();
        }
      }
    },
    handleCapture(image){
      console.log("handleCapture")
      this.image = image;
    },
    
    
  }
};
</script>

在src\views\system\user\profile\index.vue中调用UserFaceList组件

            <el-tab-pane label="人脸登陆" name="userFace">
              <UserFaceList v-if="activeTab=='userFace'" />
            </el-tab-pane>

效果如下

后续设计system_user_face表结合system_users一起实现多对一人脸登陆。

=============    相关博文   ============

钉钉扫码登录ruoyi-vue-pro——超详细

使用码云gitee登录ruoyi-vue-pro——坑比较多

Vue2 打包部署后通过修改配置文件修改全局变量——实时生效

只需3句让Vue3 打包部署后通过修改配置文件修改全局变量——实时生效

=============    相关博文   ============

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值