vue3.0 + ts H5拍照详细代码

pc端调用摄像头,本地实现H5拍照功能;
解决拍照模糊问题;
解决角度问题;

文件目录

-----wlCamera
-------img
----------camera.png
----------left.png
----------right.png
-----------tip,png
-------less
----------index.modules.less
-------ts
----------h5.ts
----------interface.ts
-------index.tsx

index.tsx

import { defineComponent, reactive, ref } from "vue";
import { Modal, Button } from 'view-ui-plus';
import tipPng from "./img/tip.png";
import cameraPng from "./img/camera.png";
import leftPng from "./img/left.png";
import rightPng from "./img/right.png";
import H5 from "./ts/h5"
import styles from "./less/index.module.less"
import {Props} from "./ts/interface"
export default defineComponent({
  emits: ["FileEvent"],
  props: {
    fileName: {
      type: String,
      required: true
    },
    ratio: {
      type: Object,
      default: () => {
        return {
          width: 1280,
          height: 720
        }
      }
    }
  },
  setup (props: Props, {emit, slots}) {
    let W = props.radio?.width || 1280, H = props.radio?.height || 720;
    let url = ref<string>(""),
      text = ref<string>("请点击左上角允许访问摄像头。"),
      popVb = ref<boolean>(false),
      textShow = ref<boolean>(true),
      popCamera = ref<boolean>(false),
      imgUtilSrc = ref<string>(""),
      videoEle: HTMLVideoElement,
      cameraH5 = new H5({radio: {width: W, height: H}, fileName: props.fileName});
    const videoRef = ref<any>(null);
    cameraH5.navigatorExtendH5();
    const onClick = () => {
      popCamera.value = true;
      cameraH5.getCaremaPermissionsH5(stream => {
        if (typeof stream == "string") {
          text.value = stream;
          textShow.value = true;
          popVb.value = stream == "系统无访问摄像头权限,配置后才能使用。";
          url.value = location.origin;
        } else {
          popVb.value = false;
          textShow.value = false;
          videoEle = document.getElementById("video") as HTMLVideoElement;
          videoEle.srcObject = stream;
          videoEle.onloadedmetadata = function (e) {
            videoEle.play();
          };
        }
      })
    }
    const Camera = () => {
      let file: File = cameraH5.createCanvas(videoEle);
      cameraH5.CloseVideoH5();
      popCamera.value = false;
      emit("FileEvent", file);
    }
    const rotateLeft = () => {
      cameraH5.rotateCount -= 1;
      videoRef.value.style.transform = 'translate(-50%, -50%) rotate(' + 90 * cameraH5.rotateCount +'deg)';
      imgUtilSrc.value = cameraH5.setImageSrc(videoEle);
    }
    const rotateRight = () => {
      cameraH5.rotateCount += 1;
      videoRef.value.style.transform = 'translate(-50%, -50%) rotate(' + 90 * cameraH5.rotateCount +'deg)';
      imgUtilSrc.value = cameraH5.setImageSrc(videoEle);
    }
    return () => (
      <>
        <span onClick={onClick}>{slots.default?.() || <Button>拍照</Button>}</span>
        <Modal z-index={9999} v-model={popVb.value} title="多媒体设置" width={900} footer-hide={true} draggable={true}>
          <div id="chrome-tip-dialog" class="pd20">
            <p>1. 打开在浏览器地址栏中输入 chrome://flags/#unsafely-treat-insecure-origin-as-secure</p>
            <p>2. Insecure origins treated as secure 切换成 Enabled 状态, 如图所示:</p>
            <div class="img-camera-tip">
              <img src={tipPng} alt=""></img>
            </div>
            <p>3. 输入框中输入域名 {url.value}</p>
            <p>4. 点击重启后生效。</p>
          </div>
        </Modal>
        <Modal v-model={popCamera.value} z-index={1010} title="拍照" width={1280} styles={{top: "10px"}} footer-hide={true} draggable={true} onOnCancel={() => cameraH5.CloseVideoH5()}>
          <div class={styles.content}>
            <div class={styles.tip} v-show={textShow.value}>{text.value}</div>
            <video ref={videoRef} class={styles.video} id="video"></video>
            <canvas id="canvas" class={styles.canvas}></canvas>
            <div v-show={!textShow.value} class={styles.toolBtns}>
              <div class={styles.btn} onClick={rotateRight}>
                {slots.iconRight?.() || <img class={styles.btnImg} title="右转" src={rightPng} alt="" />}
              </div>
              <div class={styles.btn} onClick={Camera}>
                {slots.iconCamera?.() || <img class={styles.btnCameraImg} title="拍照" src={cameraPng} alt="" />}
              </div>
              <div class={styles.btn} onClick={rotateLeft}>
                {slots.iconLeft?.() || <img class={styles.btnImg} title="左转" src={leftPng} alt="" />}
              </div>
             
            </div>
          </div>
        </Modal>
      </>
    )
  }
})

ts

h5.ts


import {Props} from "./interface"
export default class H5 {
  private ratioWidth: number;
  private ratioHeight: number;
  private fileName: string;
  rotateCount: number;
  private Angle: number;
  MediaStreamTrack: any;
  constructor (cfg: Props) {
    this.ratioWidth = cfg.radio?.width || 1280;
    this.ratioHeight = cfg.radio?.height || 720;
    this.fileName = cfg.fileName;
    this.rotateCount = 0;
    this.Angle = 0;
  }
  navigatorExtendH5 () {
    if (navigator.mediaDevices.getUserMedia === undefined) {
      navigator.mediaDevices.getUserMedia = function (constraints: MediaStreamConstraints) {
        var getUserMedia = navigator.getUserMedia;
        if (!getUserMedia) {
            return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
        }
        return new Promise(function (resolve, reject) {
            getUserMedia.call(navigator, constraints, resolve, reject);
        });
      }
    }
  }
  getCaremaPermissionsH5 (cb) {
    window.navigator.mediaDevices.getUserMedia({ audio: false, video: {width: this.ratioWidth, height: this.ratioHeight} }).then(stream  => {
      this.MediaStreamTrack = stream.getTracks()[0];
      cb && cb(stream);
    }).catch(err => {
      var tip = '系统无访问摄像头权限,配置后才能使用。'
      if (err.name == 'PermissionDeniedError') {
        tip = '无权限访问摄像头'
      } else if (err.name.indexOf('NotFoundError') > -1) {
        tip = '摄像头未连接'
      }
      cb && cb(tip);
    })
  }
  transfer (stream: MediaStream, cb) { // 录制视频
    const mediaRecorder = new MediaRecorder(stream);
    let recordedBlobs: Blob[] = [];
    mediaRecorder.ondataavailable = (event) => {
      if (event.data && event.data.size > 0) {
        recordedBlobs.push(event.data);
      }
    };
    mediaRecorder.onstop = function(e) {
      const blob = new Blob(recordedBlobs, { type: 'video/mp4' });
      cb && cb(blob)
    }
    mediaRecorder.start();
    setTimeout(function () {
      mediaRecorder.stop();
    }, 10000);
  }
  dataURLtoFile (dataurl) {
    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
    while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new File([u8arr], this.fileName, {type: mime});
  }
  getPX (ele, property) {
    return Number(ele.style[property].slice(0, -2))
  }
  createCanvas (videoEle) {
    let fileUrl = this.setImageSrc(videoEle);
    return this.dataURLtoFile(fileUrl);
  }
  CloseVideoH5 () {
    this.rotateCount = 0;
    this.Angle = 0;
    this.MediaStreamTrack && this.MediaStreamTrack.stop();
  }
  setImageSrc (videoEle) {
    var canvas = document.createElement('canvas');
    var ctx: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
    canvas.width = this.ratioWidth;
    canvas.height = this.ratioHeight;
    this.Angle = (90 * this.rotateCount) % 360
    if (this.Angle < 0) {
      this.Angle += 360
    }
    switch (this.Angle) {
      case 90:     // 旋转90度
        canvas.width = this.ratioHeight;
        canvas.height = this.ratioWidth;    
        ctx.rotate(Math.PI / 2);
        ctx.drawImage(videoEle, 0, -this.ratioHeight, this.ratioWidth, this.ratioHeight);
        break;
      case 180:     // 旋转180度
        ctx.rotate(Math.PI);    
        ctx.drawImage(videoEle, -this.ratioWidth, -this.ratioHeight, this.ratioWidth, this.ratioHeight);
        break;
      case 270:     // 旋转270度
        canvas.width = this.ratioHeight;    
        canvas.height = this.ratioWidth;    
        ctx.rotate(3 * Math.PI / 2);    
        ctx.drawImage(videoEle, -this.ratioWidth, 0, this.ratioWidth, this.ratioHeight);
        break;
      default:
        ctx.drawImage(videoEle, 0, 0, this.ratioWidth, this.ratioHeight);
    }
    return canvas.toDataURL('image/png');
  }
}

interface.ts

interface Radio {
  width: number,
  height: number,
}
interface Props {
  fileName: string,
  radio?: Radio
}
export {Radio, Props}

img

在这里插入图片描述

less

.video, .canvas {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  position: absolute;
  transform-origin: center center;
  margin: 0 auto;
}
.content {
  position: relative;
  text-align: center;
  border: 5px dashed #999;
  margin: 0 auto;
  overflow: hidden;
  height: 720px;
}
.tip {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  position: absolute;
  font-size: 16px;
  color: #E6A23C;
}
.toolBtns {
  position: absolute;
  bottom: 20px;
  width: 100%;
  height: 40px;
  line-height: 40px;
}
.btn {
  width: 40px;
  margin: 0 5px;
  cursor: pointer;
  display: inline-block;
}
.btnImg {
  width: 40px;
}
.btnCameraImg {
  width: 35px;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值