h5移动端使用video实现拍照、上传文件对象、选择相册,做手机兼容。

发布时间 2023-12-05 10:09:53作者: 刘星Stars
html部分
<template>
  <div class="views">
    <video style="width: 100vw; height: calc(100vh - 18vh)" object-fit="fill"></video>
    <!-- <img style="width: 100vw; height: calc(100vh - 18vh)" :src="str" alt="" srcset=""> -->
    <div class="picture" @click="pictureClick">
      <i class="iconfont icon-picture"></i>
    </div>
    <div @click="handlePhotographClick" class="action"></div>
    <div class="folder" @click="folderClick">
      <i class="iconfont icon-folder"></i>
    </div>
    <div class="bac">
      <div>
        <div class="img_box" v-for="(img, index) in srcList" :key="img.src + index">
          <i class="iconfont icon-guanbi" @click="delImg(img.name, index)"></i>
          <img src="../../../../public/img/files1.png" v-if="img.name === 'files'" />
          <img :src="img.src" v-else />
          <span>{{ index + 1 }}</span>
        </div>
      </div>
      <div>
        <button class="btn" @click="upload">确认</button>
      </div>
    </div>
  </div>
</template>

js部分

<script>
export default {
  data() {
    return {
      imageUrl: '',
      // 媒体流,用于关闭摄像头
      mediaStreamTrack: null,
      fileName: '', // 上传文件名
      fileList: [], // 上传文件列表
      isCamera: true, // 是否是摄像头
      imgBase64: '', // 图片base64
      photoList: [], // 图片列表
      localHeight: 0, // 本地视频高度
      srcList: [], // 图片路径列表
    }
  },
  mounted() {
    this.invokingCamera()
  },
  destroyed() {
    this.handlePhotographCloseClick()
  },
  methods: {
    // 调用摄像头
    invokingCamera() {
      const self = this
      // 注意本例需要在HTTPS协议网站中运行,新版本Chrome中getUserMedia接口在http下不再支持。
      // 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
      if (navigator.mediaDevices === undefined) {
        navigator.mediaDevices = {}
      }
      // 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
      // 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
      if (navigator.mediaDevices.getUserMedia === undefined) {
        navigator.mediaDevices.getUserMedia = function (constraints) {
          // 首先,如果有getUserMedia的话,就获得它
          const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia
          // 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
          if (!getUserMedia) {
            return Promise.reject(new Error('getUserMedia is not implemented in this browser'))
          }
          // 否则,为老的navigator.getUserMedia方法包裹一个Promise
          return new Promise(function (resolve, reject) {
            getUserMedia.call(navigator, constraints, resolve, reject)
          })
        }
      }
      const constraints = {
        audio: false,
        video: {
          // 前置摄像头
          facingMode: { exact: 'environment' },
          // 手机端相当于高
          width: Math.max(window.innerWidth, window.innerHeight),
          // 手机端相当于宽
          height: Math.min(window.innerWidth, window.innerHeight),
        },
      }
      navigator.mediaDevices
        .getUserMedia(constraints)
        .then(function (stream) {
          self.mediaStreamTrack = stream
          const video = document.querySelector('video')
          // 旧的浏览器可能没有srcObject
          if ('srcObject' in video) {
            video.srcObject = stream
          } else {
            // 防止在新的浏览器里使用它,应为它已经不再支持了
            video.src = window.URL.createObjectURL(stream)
          }
          video.onloadedmetadata = function (e) {
            video.play()
          }
        })
        .catch(function (err) {
          console.log(err.name + ': ' + err.message)
        })
    },
    // 关闭摄像头
    handlePhotographCloseClick() {
      if (this.mediaStreamTrack) {
        // 关闭摄像头
        this.mediaStreamTrack.getTracks().forEach(function (track) {
          track.stop()
        })
        this.mediaStreamTrack = null
      }
    },
    // 拍照
    handlePhotographClick() {
      const canvas = document.createElement('canvas')
      const ctx = canvas.getContext('2d')
      const video = document.querySelector('video')
      canvas.width = Math.min(video.videoWidth, video.videoHeight)
      canvas.height = Math.max(video.videoWidth, video.videoHeight)
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
      // 将图片转为base64
      const base64Image = canvas.toDataURL('image/png')
      // const str = base64Image.replace('data:image/png;base64,', '')
      // // 文件对象数组
      // this.photoList.push(this.convertBlobToFile(this.convertBase64ToBlob(str), new Date().getTime()))
      // 图片路径数组
      this.srcList.push({
        src: base64Image,
        // src: this.str,
        name: new Date().getTime() + '.png',
      })
    },
    folderClick() {
      // 选择文件
      var input = document.createElement('input')
      input.type = 'file'
      input.accept = '.doc,.docx,.txt,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,.pdf'
      input.addEventListener('change', (e) => {
        // console.log(e.target.files, 'eeeeee')
        // 读取选择的文件
        this.fileList.push(e.target.files)
        this.srcList.push({
          src: null,
          name: 'files',
        })
      })
      // 触发点击事件,打开文件
      input.click()
    },
    pictureClick() {
      const self = this
      // 选择图片
      var input = document.createElement('input')
      input.type = 'file'
      input.accept = 'image/*'
      input.multiple = true
      // 点击事件处理函数
      input.addEventListener('change', function () {
        if (this.files && this.files[0]) {
          // 读取选择的图片文件
          var reader = new FileReader()
          reader.onload = (e) => {
            // 图片加载完成后,将图片URL赋值给input的src属性,即可显示图片
            // console.log(e.target.result, 'e.target.result')
            const type = this.files[0].type.split('/')[1]
            self.srcList.push({
              src: e.target.result,
              name: new Date().getTime() + '.' + type,
            })
          }
          reader.readAsDataURL(this.files[0])
        }
      })
      // 触发点击事件,打开图库
      input.click()
    },
    // 转换为blob格式
    convertBase64ToBlob(base64Str) {
      // 将base64字符串转为二进制数据
      const byteCharacters = atob(base64Str)
      // 创建Blob对象
      const blob = new Blob([byteCharacters], { type: 'application/octet-stream' })
      return blob
    },
    // 将blob转为file对象
    convertBlobToFile(blob, fileName) {
      // 创建File对象
      const type = fileName.split('.')[1]
      const file = new File([blob], fileName, { type: `image/${type}` })
      return file
    },
    delImg(name, index) {
      // 删除图片
      if (name === 'files') {
        this.fileList.splice(index, 1)
      }
      this.srcList.splice(index, 1)
    },
    upload() {
      // 上传按钮
      if (this.srcList.length > 0) {
        for (let i = 0; i < this.srcList.length; i++) {
          // 文件对象数组
          if (this.srcList[i].src === null) {
            continue
          }
          const type = this.srcList[i].name.split('.')[1]
          this.photoList.push(this.convertBlobToFile(this.convertBase64ToBlob(this.srcList[i].src.replace(`data:image/${type};base64,`, '')), new Date().getTime() + `.${type}`))
        }
      }
      const fileObj = []
      for (let i = 0; i < this.fileList.length; i++) {
        fileObj.push(this.fileList[i][0])
      }
      for (let i = 0; i < this.photoList.length; i++) {
        fileObj.push(this.photoList[i])
      }
      console.log(fileObj, 'fileObj')
    },
  },
}
</script>

css部分

<style scoped lang="stylus">
.views{
  width: 100vw;
  height: 100vh;
  background-color: #ccc;
  position: relative

  .picture{
    position: absolute;
    left: 1rem;
    z-index: 9;
    bottom: 4rem;
    .icon-picture{
    font-size: 1rem;
    font-weight: bold;
    }
  }
  .folder{
    position: absolute;
    right: 1rem;
    z-index: 9;
    bottom: 4rem;
   .icon-folder{
      font-size: 1rem;
      font-weight: bold;
    }
  }
  .action{
  position: absolute;
  bottom: 20vh;
  left: 50%;
  margin-left: -35px;
  border-radius: 50px;
  border: 10px solid #ccc;
  box-shadow: 0 0 10px black;
  background-color: #fff;
  width: 70px;
  height: 70px;
  display: flex;
  justify-content: center;
  z-index :99
  }
.bac {
    overflow-x: scroll;
    white-space: nowrap; /* 横向内容不换行 */
    align-items: flex-end; /* 图片索引在左下角 */
  }

  .img_box {
    margin-top: 10px;
    position: relative;
    display: inline-block; /* img_box横向排列 */
    margin-left: 8px;
    margin-bottom: 8px;
  }

  .icon-guanbi {
    position: absolute;
    top: -10px;
    right: -10px;
    width: 20px;
    height: 20px;
    font-size : 20px;
    color: #fff;
    color:red;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
  }

  img {
    // width: 100px;
    height:15vh;
    margin-left: 8px
  }

  span {
    position: absolute;
    bottom: 0;
    left: 0;
    margin-left: 4px;
    margin-bottom: 4px;
    color: #fff;
    background-color: #64e8ff;
    padding: 4px;
    font-size: 20px;
    border-radius: 2px 2px 2px 6px;
  }
  .btn{
    color: #fff;
    border: none;
    background: #029afc;
    height: 1rem;
    width: 1.5rem;
    position: absolute;
    right: 0;
    bottom: 1rem;
    z-index: 99;
    border-radius: 10px;
    line-height: 1rem;
  }
}
</style>

以上代码可以直接复制使用,留个关注吧。