当前位置: 首页 > news >正文

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"/>

        <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调用摄像头拍照兼容性及原生实现拍照取景框 - 简书

相关文章:

  • 字节跳动青训营--前端day6
  • 浅谈估值模型:PB指标与剩余收益估值
  • 深度解读 python 实现 dbscan算法
  • 2023年“华数杯”国际大学生数学建模A题完整思路
  • C++ 入门
  • 机器学习研究的 12 个宝贵经验
  • LabVIEW更高的吞吐量与更少的延迟2
  • 【C语言练习】杨氏矩阵、杨辉三角
  • 【Java】到底什么是包?|最通俗易懂讲解|保姆级
  • 【YBT2023寒假Day4 B】人人人数(数学)
  • MasterSlave概念与配置与eeprom信息擦除解决步骤
  • FPGA和CPLD芯片选型介绍(二)
  • 【C++】继承
  • 第九层(10):STL之函数对象
  • 边缘检测与角点检测(模式识别与图像处理课程作业)
  • [原]深入对比数据科学工具箱:Python和R 非结构化数据的结构化
  • Centos6.8 使用rpm安装mysql5.7
  • JAVA之继承和多态
  • Koa2 之文件上传下载
  • Linux Process Manage
  • mockjs让前端开发独立于后端
  • Python 使用 Tornado 框架实现 WebHook 自动部署 Git 项目
  • vue数据传递--我有特殊的实现技巧
  • 阿里云容器服务区块链解决方案全新升级 支持Hyperledger Fabric v1.1
  • 前端学习笔记之原型——一张图说明`prototype`和`__proto__`的区别
  • 由插件封装引出的一丢丢思考
  • 与 ConTeXt MkIV 官方文档的接驳
  • 原生Ajax
  • Python 之网络式编程
  • # 计算机视觉入门
  • ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTr
  • (6)【Python/机器学习/深度学习】Machine-Learning模型与算法应用—使用Adaboost建模及工作环境下的数据分析整理
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (分享)自己整理的一些简单awk实用语句
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (附源码)ssm教材管理系统 毕业设计 011229
  • (附源码)ssm考试题库管理系统 毕业设计 069043
  • (简单有案例)前端实现主题切换、动态换肤的两种简单方式
  • (九)信息融合方式简介
  • (深度全面解析)ChatGPT的重大更新给创业者带来了哪些红利机会
  • (学习日记)2024.04.10:UCOSIII第三十八节:事件实验
  • (转) ns2/nam与nam实现相关的文件
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • .net core 客户端缓存、服务器端响应缓存、服务器内存缓存
  • .NET I/O 学习笔记:对文件和目录进行解压缩操作
  • .NET NPOI导出Excel详解
  • .NET/C# 编译期能确定的字符串会在字符串暂存池中不会被 GC 垃圾回收掉
  • .NET多线程执行函数
  • [ C++ ] 继承
  • [ solr入门 ] - 利用solrJ进行检索
  • [\u4e00-\u9fa5] //匹配中文字符
  • [2010-8-30]
  • [BT]BUUCTF刷题第8天(3.26)
  • [BZOJ 1032][JSOI2007]祖码Zuma(区间Dp)