这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

背景

在实际项目中,遇到了需要唤起手机摄像头拍照的需求,最开始是通过<input type="file" hidden accept="image/*" capture="camera" />的方式,可以直接唤起手机相机,但是用户拍照的方向各式各样,导致后续业务处理时,没有达到预期的效果。

基于此,产品同学期望能在用户拍照时给用户一个引导框(也就是平时我们在用第三方证件拍照时的取景框效果)。经过讨论,给出了两种解决方案,一种是通过我们自研,先尝试看一下效果,第二种是使用第三方的 SDK,仅使用他们的拍照功能。

本文档仅涉及第一种,即通过我们自研的方式,实现 H5 拍照选景框的效果。

技术方案

最终效果示例

核心实现

1、核心实现:利用 navigator.mediaDevices.getUserMedia 打开摄像头,将视频流放入 video 标签的 src 中,再通过 canvas.drawImage 的方法,以 video 对象为画布源,绘制最终拍照的图片。

2、代码示例:

1)HTML 示例

<div id="cameraContainer">
<video id="cameraView" width="345" height="210" autoplay></video>
<div class="frame-container">
<div class="mask"></div>
<div id="frame">
<div class="corner topLeft"></div>
<div class="corner topRight"></div>
<div class="corner bottomLeft"></div>
<div class="corner bottomRight"></div>
</div>
<div style="margin-top: 6px; text-align: center; color: red">
Please put your ID in the box
</div>
</div>
</div>
<button id="captureButton">拍照</button>
<canvas id="canvas" style="display: none"></canvas>
<img id="photo" alt="Captured Photo" />

2)JS 示例

      const video = document.getElementById("cameraView");
const frame = document.getElementById("frame");
const captureButton = document.getElementById("captureButton");
const canvas = document.getElementById("canvas");
const photo = document.getElementById("photo"); // 获取用户媒体设备权限
navigator.mediaDevices
.getUserMedia({ video: true, audio: false })
.then((stream) => {
video.srcObject = stream;
})
.catch((error) => {
console.error("获取摄像头权限失败:", error);
}); captureButton.addEventListener("click", () => {
const context = canvas.getContext("2d"); // 设置画布尺寸与取景框相同
console.log(video.videoWidth);
canvas.width = video.videoWidth;
canvas.height = video.videoHeight; // 绘制取景框内的画面到画布
context.drawImage(video, 0, 0); // 将画布内容转为图片并显示
photo.src = canvas.toDataURL();
photo.style.display = "block";
});

可运行 Demo

1、在 VS Code IDE 中,创建一个 HTML 文件,将下面的代码复制即可。

2、启动 VS Code 的 Live Server 插件(如果没有,可以安装,如果有其他方案也可),然后通过 127.0.0.1 或 localhost 的方式访问,对应的端口和路径,请按照你的 HTML 文件路径来即可。

3、注意:不要用局域网内的 IP 访问,否则会无法唤起摄像头,后面注意事项中会说明原因和解决方案。

<!DOCTYPE html>
<html>
<meta
name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"
/>
<head>
<style>
#cameraContainer {
position: relative;
width: 345px;
height: 210px;
overflow: hidden;
} #cameraView {
object-fit: cover;
}
.frame-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
} .mask {
position: absolute;
width: 100%;
height: 100%;
}
#frame {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 90px;
z-index: 10;
background-color: transparent;
} .corner {
position: absolute;
border-color: red;
border-style: solid;
padding: 6px;
} .topLeft {
top: 1px;
left: 1px;
border-width: 2px 0 0 2px;
} .topRight {
top: 1px;
right: 1px;
border-width: 2px 2px 0 0;
} .bottomLeft {
bottom: 1px;
left: 1px;
border-width: 0 0 2px 2px;
} .bottomRight {
bottom: 1px;
right: 1px;
border-width: 0 2px 2px 0;
} #photo {
display: none;
width: 345px;
height: 210px;
}
</style>
</head>
<body>
<div id="cameraContainer">
<video id="cameraView" width="345" height="210" autoplay></video>
<div class="frame-container">
<div class="mask"></div>
<div id="frame">
<div class="corner topLeft"></div>
<div class="corner topRight"></div>
<div class="corner bottomLeft"></div>
<div class="corner bottomRight"></div>
</div>
<div style="margin-top: 6px; text-align: center; color: red">
Please put your ID in the box
</div>
</div>
</div>
<button id="captureButton">拍照</button>
<canvas id="canvas" style="display: none"></canvas>
<img id="photo" alt="Captured Photo" /> <script>
const video = document.getElementById("cameraView");
const frame = document.getElementById("frame");
const captureButton = document.getElementById("captureButton");
const canvas = document.getElementById("canvas");
const photo = document.getElementById("photo"); // 获取用户媒体设备权限
navigator.mediaDevices
.getUserMedia({ video: true, audio: false })
.then((stream) => {
video.srcObject = stream;
})
.catch((error) => {
console.error("获取摄像头权限失败:", error);
}); // 拍照按钮点击事件
captureButton.addEventListener("click", () => {
const context = canvas.getContext("2d"); // 设置画布尺寸与取景框相同
console.log(video.videoWidth);
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
// 绘制取景框内的画面到画布
context.drawImage(video, 0, 0); // 将画布内容转为图片并显示
photo.src = canvas.toDataURL();
photo.style.display = "block";
});
</script>
</body>
</html>

注意事项

1、在实际项目中,需要注意做好容错,可以参考 MDN 中的容错代码,如果无法唤起手机摄像头(用户拒绝、浏览器不支持等),则需要根据实际情况,考虑兼容方案(给出提示、直接唤起原生相机等)

兼容代码如下(developer.mozilla.org/zh-CN/docs/…

// 老的浏览器可能根本没有实现 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: true, video: true })
.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);
});

2、当业务逻辑获取了 canvas 绘制的图片后,出于性能以及交互体验的考虑,应该关闭 video 播放、以及摄像头,可以参考如下代码:

// 停止 video 播放
// 在合适的地方,保存之前设置 video.src 的 video 对象引用
video.stop() // 关闭摄像头
// 在 navigator.mediaDevices.getUserMedia().then((stream)=>{ //do something }) 中,保存 stream 对象引用
stream?.getTracks()?.forEach(function(track) {
track.stop()
})

3、本地跑 Demo 时,可以通过 localhost 或 127.0.0.1 的域名方式访问,此时是可以唤起摄像头,但如果用局域网的 IP 则不行,这是因为浏览器的安全限制,必须使用 https 才可以。此时有两种解决方案(仅应用于本地调试)

1)在将 Demo 相关的逻辑放入实际项目中时,启动项目时,如果也支持 localhost / 127.0.0.1 访问,则没有问题

2)如果本地能够支持 https 访问,则也可以唤起摄像头

3)如果上述均不可,则可以设置 chrome 浏览器的安全策略,将对应的域名或 IP 地址,打开为白名单,具体设置方式,请参考 juejin.cn/post/699030…

4、请务必保证线上业务是 https 协议,否则无法正常打开摄像头

风险点及待办

风险点

1、Demo 仅在电脑上尝试,不保证手机的兼容性问题及效果(正式上线前需要QA和产品做相关的兼容测试)

2、因为使用了 video 标签,蒙层也是在 video 标签上盖的,这里可能会涉及到 video 标签的兼容性问题,就是在不同的手机浏览器上,video 标签的优先级可能会很高,导致蒙层或遮罩无法盖住 video 标签(国产手机浏览器为重灾区)

待办

1、针对 Canvas 的取景框遮罩效果,暂未实现,后续如果有要求,可以考虑用图片替换,即让 UI 同学直接出一个镂空的取景框图片,直接替换对应元素即可

2、针对 Canvas 仅绘制取景框内容的功能未实现,这里涉及到如何调整 drawImage 相关的参数,以及裁切绘制后的图片是否可以被业务方所识别的问题,如果参数调整的不合适,会出现图片变形,模糊的情况

本文转载于:

https://juejin.cn/post/7327353533618978842

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录--H5 实现拍照选景框效果的更多相关文章

  1. JQuery+CSS3实现封装弹出登录框效果

    原文:JQuery+CSS3实现封装弹出登录框效果 上次发了一篇使用Javascript来实现弹出层的效果,这次刚好用了JQuery来实现,所以顺便记录一下: 因为这次使用了Bootstrap来做一个 ...

  2. vue实现穿梭框效果

    vue实现穿梭框效果 一.总结 一句话总结: 用两个数组分别记录左右框框里面的值,用两个数组绑定checkbox,用来记录选中的checkbox值,根据选中的checkbox的值实现删除增加即可 1. ...

  3. jQuery动态提示消息框效果

    效果预览:http://keleyi.com/keleyi/phtml/jqtexiao/2.htm 原文:http://keleyi.com/a/bjac/hxv86dyi.htm <!DOC ...

  4. Combox 实现百度收索框效果

    标题中所谓百度收缩框效果,就是在输入数据的时候,自动提示,来张图就明白了: 用Combox来实现这个功能只是需要设置三个A开头的属性就OK了:AutoCompleteSource.AutoComple ...

  5. 【转】提示框第三方库之MBProgressHUD iOS toast效果 动态提示框效果

    原文网址:http://www.zhimengzhe.com/IOSkaifa/37910.html MBProgressHUD是一个开源项目,实现了很多种样式的提示框,使用上简单.方便,并且可以对显 ...

  6. JS组件Bootstrap实现弹出框和提示框效果代码

    这篇文章主要介绍了JS组件Bootstrap实现弹出框和提示框效果代码,对弹出框和提示框感兴趣的小伙伴们可以参考一下 前言:对于Web开发人员,弹出框和提示框的使用肯定不会陌生,比如常见的表格新增和编 ...

  7. jQuery实现鼠标移到元素上动态提示消息框效果

    当光标移动到某些元素上时,会弹出像tips的提示框,这种效果想必大家都有见到过吧,下面有个不错的示例,大家可以感受下 当光标移动到某些元素上时,会弹出像tips的提示框. 复制代码代码如下: < ...

  8. WPF提示框效果

    WPF提示框效果 1,新建WPF应用程序 2,添加用户控件Message 3,在Message中编写如下代码 <Border x:Name="border" BorderTh ...

  9. Android较低版本(<5.2) 页面默认Select选择框效果的BUG解决

    Bug描述: 使用低版本安卓(<5.2),在微信上打开网页,点击下拉框,会出现如下图所示的用来展示select选项的弹出框: 在选项较少的时候,可以向下滑动,将选项滑到底部 滑动前: 滑动后: ...

  10. CSS发光边框文本框效果

    7,166 次阅读 ‹ NSH Blog 网页设计 CSS发光边框文本框效果 或许你看过Safari浏览器下,任何输入框都会有一个发光的蓝色边框,这不单纯只是蓝色边框而已,其实包含了许多CSS3技巧知 ...

随机推荐

  1. JS LeetCode 1423. 可获得的最大点数简单题解

    壹 ❀ 引 最近也是浮躁的很,一篇redux的文章写了三千多字才算写了一半...写的泪目了.还是刷刷算法静下心,顺带记录下算法做题过程吧.今天的题来自LeetCode每日打卡,题目出自LeetCode ...

  2. 【Unity3D】UGUI之Slider

    1 Slider属性面板 ​ 在 Hierarchy 窗口右键,选择 UI 列表里的 Slider 控件,即可创建 Slider 控件,选中创建的 Slider 控件,按键盘[T]键,可以调整 Sli ...

  3. spring boot使用拦截器修改请求URL

    假如我要将请求路径中/foobar都去掉? 1.定义拦截器 package com.laoxu.test.helloweb; import org.springframework.stereotype ...

  4. Java集合框架学习(九) TreeMap详解

    TreeMap介绍 TreeMap 类实现了Map接口,和HashMap类类似. TreeMap是一个基于Red-Black tree的可导航map的实现. 它基于key的自然顺序排序. TreeMa ...

  5. SpringBoot整合EasyExcel实现Excel表格的导出功能

    前言 大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教. 在后端管理系统的开发中,经常有导出当前表格数 ...

  6. 实操开源版全栈测试工具RunnerGo安装(三)MacOS安装

    以Sonoma 14.1.2系统为例 视频教程:https://www.bilibili.com/video/BV1fG411e7h2/?spm_id_from=333.999.0.0 1.下载并安装 ...

  7. Ubuntu常用工具和问题整理

    安装Ubuntu虚拟机时常会遇到的几个问题 1.安装时设置镜像 安装Ubuntu系统时设置国内镜像可以加快安装速度:http://mirrors.aliyun.com/ubuntu/ 参考:ubunt ...

  8. iOS上拉边界下拉白色空白问题解决概述

    表现 手指按住屏幕下拉,屏幕顶部会多出一块白色区域.手指按住屏幕上拉,底部多出一块白色区域. 产生原因 在 iOS 中,手指按住屏幕上下拖动,会触发 touchmove 事件.这个事件触发的对象是整个 ...

  9. 【App Service】遇见本地访问Azure App Service应用慢或者是调用第三方接口慢的调试小工具

    问题描述 当应用部署到微软云 Azure后,如果遇见本地访问Azure App Service应用慢或者是调用第三方接口慢的时候,有什么好的调试方法呢? 来判断具体时那一段请求耗时呢? 问题解答 当然 ...

  10. 【Azure 存储服务】Azure Data Lake Storage (ADLS) Gen2 GRS Failover是否支持自动切换或者手动切换到灾备的终结点呢?

    问题描述 在Azure的存储服务中,介绍灾备恢复和Storage Account故障转移的文档中,有一句话"Account failover is not supported for sto ...