h5实现相机

什么是取景器

取景器是什么?取景器是相机的一个专业术语,在前端就是扫描拍照

取景器的实现原理

请求手机的一个媒体类型的视频轨道,利用一个div或者图片作为上层蒙层,然后在利用canvas绘制视频中某一帧的画面绘制为图片。

前期知识准备

- # MediaDevices.getUserMedia()

MediaDevices.getUserMedia() - Web API 接口参考 | MDN 在mdn中介绍了,这个api会调取摄像头,获取一个媒体轨道,这里我们只需要重点关注视频轨道,mdn对api的讲解也很清楚,下面是mdn的做好兼容的代码只需要直接使用

值得注意的点

  1. 该api必须https
  2. 兼容老版本浏览器
// 老的浏览器可能根本没有实现 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;

    // 一些浏览器根本没实现它 - 那么就返回一个 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);
    });
  }
}

navigator.mediaDevices.getUserMedia(
{
                audio: false, //不需要获取声音
                video: {
                    //后置摄像头
                    facingMode: 'environment',
                    //分辨率,理想是2040,如果没有,最小1280,没有设置会比较模糊
                    width: { min: 1280, ideal: 2040 },
                    height: { min: 720, ideal: 1080 },
                },
            }
)
.then(function(stream) {
  var 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);
});
  • canvas绘图-drawImage

HTML5 canvas drawImage() 方法 canvas绘制,主要是要注意绘制过程中的参数,w3cschool也写的非常清楚,暂不做重复说明

案例源码

html部分

 <div class="viewfinder">
            <!-- 用于兼容,调用手机拍照功能 -->
            <input id="file" type="file" accept="image/*" capture="camera" style="display:none">
            <!--兼容ios的微信内置浏览器 -->
            <button v-if="ioswx && status == 0" class="start" @click="start">开始</button>
            <button v-if="status == 2" class="again" @click="butonClick(1)">重拍</button>
            <button v-if="status == 2" class="finsh" @click="butonClick(2)">完成</button>

            <div class="wrap-box">
                <video playsinline style="height: 100vh;width: 100vw;position: fixed;top: 0;left: 0;object-fit: cover"/>

                <div class="imgshow">
                    <img v-if="status==1" src="../../../public/images/2.png" class="box">
                    <img v-if="status==2" :src="imageUrl" alt="" class="wrapImg">
                </div>

                <div v-if="status == 1 " class="snap" @click="snapPhoto"/>
                <canvas id="mycanvas" style="visibility: hidden;"/>
            </div>
        </div>

css部分

<style lang="less" scoped>
.wrap-box {
    width: 100%; 
    position: fixed; 
    left: 0; 
    height: 100%;
    .imgshow {
        width: 100%; 
        position: absolute; 
        left: 0; 
        bottom: 25vh; 
        top: 25vh; 
        right: 0;
    }
    .box {
        width: 100%;
        height: 100%;
        border: 10px solid rgba(0,0,0,0.5);box-sizing: border-box;
    }
    .wrapImg{
        width:100%;
        height: 100%;
    }
    .snap {
        position: fixed; 
        left: 0;
        top: 2vh;
        z-index: 1000;
    }
}
</style>

js逻辑部分

<script>
export default {
    data() {
        return {
            status: 0, // 自定义相机-拍摄进度:0|未开启 1|开启但未拍摄 2|开启且已拍摄
            imageUrl: '',
            ioswx: false,
            cameraShow: true,
        };
    },
    mounted() {
        this.ioswx = this.isWXandIos();
        this.openCamera();
    },
    methods: {
        isWXandIos() {
            const aegent = navigator.userAgent.toLowerCase();
            if (/micromessenger/.test(aegent) && /iPhone|mac|iPod|iPad/i.test(aegent)) {
                return true;
            }

            return false;
        },
        start() {
            const video = document.querySelector('video');
            video.play();
            this.status = 1;
        },
        async openCamera() {
            const constraints = {
                audio: false,
                video: {
                    // facingMode: (this.front ? 'user' : 'environment'),
                    facingMode: 'environment',
                    width: { min: 1280, ideal: 2040 },
                    height: { min: 720, ideal: 1080 },
                },
            };

            // 兼容
            // 老的浏览器没实现mediaDevices
            if (navigator.mediaDevices === undefined) navigator.mediaDevices = {};

            if (navigator.mediaDevices.getUserMedia === undefined) {
                navigator.mediaDevices.getUserMedia = function(constraints) {
                    const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;
                    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);
                    });
                };
            }

            // 获取视频流
            const that = this;
            navigator.mediaDevices.getUserMedia(constraints)
                .then(function(stream) {
                    const video = document.querySelector('video');
                    video.srcObject = stream;
                    video.onloadedmetadata = function() {
                        video.play();
                    };
                    // 不是微信内置浏览器
                    if (!that.ioswx) {
                        that.status = 1;
                    }
                })
                .catch(function(err) {
                    console.log('nonononon', JSON.stringify(err));
                    // 调用原始摄像头;
                    that.originCamera();
                });
        },
        originCamera() {
            // const that = this;
            const promise = new Promise(function(resolve, reject) {
                const file = document.getElementById('file');
                file.click();
                console.log('fileclick');
                file.onchange = function(event) {
                    if (!event) {
                        reject('empty');
                    }
                    const file = event.target.files[0];
                    resolve(file);
                };
            });
            promise.then(value => {
                console.log('vaue', value);
                // that.submitPhoto('origin', value);
            }
            );
        },
        snapPhoto() {
            console.log(111, 'paizhao');
            const canvas = document.querySelector('#mycanvas');
            const video = document.querySelector('video');
            const width = canvas.width = video.videoWidth;
            const height = canvas.height = video.videoHeight;
            // let cut_y = height/4
            // canvas.getContext('2d').drawImage(video, 0, cut_y, width, cut_y*2, 0, 0, width, height);
            canvas.getContext('2d').drawImage(video, 0, 0, width, height);
            this.imageFile = this.canvasToFile(canvas);

            const p = new Promise(resolve => {
                canvas.toBlob(blob => {
                    const url = URL.createObjectURL(blob);
                    resolve(url);
                });
            });
            const that = this;
            p.then(value => {
                that.imageUrl = value;
                that.status = 2;
            });
        },
        canvasToFile(canvas) {
            const dataurl = canvas.toDataURL('image/png');
            let 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);
            }
            const file = new File([ u8arr ], 'phone.png', { type: mime });
            return file;
        },
        // 提交
        submitPhoto(type, file) {
            if (type == 'origin') {
                this.imageFile = file;
            }

            // 一系列的提交操作;
            // console.log('提交', this.imageFile);
            // // 在这里进行上传操作
            // // let fd=new FormData()
            // // fd.append("face_image",this.imageFile)
            // // ...
            // // 开始提示
        },
        butonClick(status) {
            if (status == 1) {
                this.status = 1;
                return;
            }

            // 提交
        },
    },
};
</script>

注意一些细节功能

在ios的微信内置浏览器中,必须用户手动调用才会自动播放视频,因此兼容ios内置浏览器需加按钮让用户自动点击

整个功能就完结啦 参考以下文章

猜你喜欢

转载自blog.csdn.net/qq_42625428/article/details/128854288