什么是取景器
取景器是什么?取景器是相机的一个专业术语,在前端就是扫描拍照
取景器的实现原理
请求手机的一个媒体类型的视频轨道,利用一个div或者图片作为上层蒙层,然后在利用canvas绘制视频中某一帧的画面绘制为图片。
前期知识准备
- # MediaDevices.getUserMedia()
MediaDevices.getUserMedia() - Web API 接口参考 | MDN 在mdn中介绍了,这个api会调取摄像头,获取一个媒体轨道,这里我们只需要重点关注视频轨道,mdn对api的讲解也很清楚,下面是mdn的做好兼容的代码只需要直接使用
值得注意的点
- 该api必须https
- 兼容老版本浏览器
// 老的浏览器可能根本没有实现 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"/>
        <div class="wrap-box">
            <!-- 视频整个页面 -->
            <video style="height: 100vh;width: 100vw;position: fixed;top: 0;left: 0;object-fit: cover"/>
            <div class="imgshow">
              
                <div v-if="status==1"  class="box"></div>
                <!-- b.拍摄完后展示抓拍图片 -->
                <img v-if="status==2" :src="imageUrl" alt="" class="wrapImg">
            </div>
            <button class="snap" @click="snapPhoto">拍照</button>
            <!-- 抓拍 -->
            <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: '',
        };
    },
    mounted() {
        //打开视频轨道
        this.openCamera();
    },
    methods: {
        openCamera() {
            const constraints = {
                audio: false,
                video: {
                    facingMode: 'environment',
                    width: { min: 1280, ideal: 2040 },
                    height: { min: 720, ideal: 1080 }
                },
            };
            // 兼容
            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();
                    };
                    that.status = 1;
                })
                .catch(function(err) {
                    // 不兼容调用原始相机拍照;
                    that.originCamera();
                });
        },
        originCamera() {
            const promise = new Promise(function(resolve, reject) {
                const file = document.getElementById('file');
                file.click();
                file.onchange = function(event) {
                    if (!event) {
                        reject('empty');
                    }
                    // 当选中或者拍摄后确定图片后
                    const file = event.target.files[0];
                    resolve(file);
                };
            });
            promise.then(value => {
                //提交照片
                that.submitPhoto('origin', value);
            }
            );
        },
        snapPhoto() {
            const canvas = document.querySelector('#mycanvas');
            const video = document.querySelector('video');
            let width = canvas.width = video.videoWidth;
            let 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保存为图片
            this.imageFile = this.canvasToFile(canvas);
            // blob转url:用于展示
            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;
            }
        },
    },
};
</script>
整个功能就完结啦 参考以下文章
- Html5调用手机摄像头后添加取景框并使用WebUploader上传 - 灰信网(软件开发博客聚合)
- 兼容性相关:h5调用摄像头拍照兼容性及原生实现拍照取景框 - 简书



















