记录--H5 实现拍照选景框效果
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
背景
在实际项目中,遇到了需要唤起手机摄像头拍照的需求,最开始是通过<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 实现拍照选景框效果的更多相关文章
- JQuery+CSS3实现封装弹出登录框效果
原文:JQuery+CSS3实现封装弹出登录框效果 上次发了一篇使用Javascript来实现弹出层的效果,这次刚好用了JQuery来实现,所以顺便记录一下: 因为这次使用了Bootstrap来做一个 ...
- vue实现穿梭框效果
vue实现穿梭框效果 一.总结 一句话总结: 用两个数组分别记录左右框框里面的值,用两个数组绑定checkbox,用来记录选中的checkbox值,根据选中的checkbox的值实现删除增加即可 1. ...
- jQuery动态提示消息框效果
效果预览:http://keleyi.com/keleyi/phtml/jqtexiao/2.htm 原文:http://keleyi.com/a/bjac/hxv86dyi.htm <!DOC ...
- Combox 实现百度收索框效果
标题中所谓百度收缩框效果,就是在输入数据的时候,自动提示,来张图就明白了: 用Combox来实现这个功能只是需要设置三个A开头的属性就OK了:AutoCompleteSource.AutoComple ...
- 【转】提示框第三方库之MBProgressHUD iOS toast效果 动态提示框效果
原文网址:http://www.zhimengzhe.com/IOSkaifa/37910.html MBProgressHUD是一个开源项目,实现了很多种样式的提示框,使用上简单.方便,并且可以对显 ...
- JS组件Bootstrap实现弹出框和提示框效果代码
这篇文章主要介绍了JS组件Bootstrap实现弹出框和提示框效果代码,对弹出框和提示框感兴趣的小伙伴们可以参考一下 前言:对于Web开发人员,弹出框和提示框的使用肯定不会陌生,比如常见的表格新增和编 ...
- jQuery实现鼠标移到元素上动态提示消息框效果
当光标移动到某些元素上时,会弹出像tips的提示框,这种效果想必大家都有见到过吧,下面有个不错的示例,大家可以感受下 当光标移动到某些元素上时,会弹出像tips的提示框. 复制代码代码如下: < ...
- WPF提示框效果
WPF提示框效果 1,新建WPF应用程序 2,添加用户控件Message 3,在Message中编写如下代码 <Border x:Name="border" BorderTh ...
- Android较低版本(<5.2) 页面默认Select选择框效果的BUG解决
Bug描述: 使用低版本安卓(<5.2),在微信上打开网页,点击下拉框,会出现如下图所示的用来展示select选项的弹出框: 在选项较少的时候,可以向下滑动,将选项滑到底部 滑动前: 滑动后: ...
- CSS发光边框文本框效果
7,166 次阅读 ‹ NSH Blog 网页设计 CSS发光边框文本框效果 或许你看过Safari浏览器下,任何输入框都会有一个发光的蓝色边框,这不单纯只是蓝色边框而已,其实包含了许多CSS3技巧知 ...
随机推荐
- FreeSWITCH添加g729编码及pcap音频提取
操作系统 : debian 11 (bullseye,docker).Windows10_x64 FreeSWITCH版本 :1.10.9 Docker版本:23.0.6 Python 版本 : ...
- pandas 用户数据分析
import pandas as pd import numpy as np from matplotlib import pyplot as plt """ 第一部分: ...
- Centos8 单机配置 Zookeeper3.6.3 集群
安装 Zookeeper 3.6.3 前提 已经安装好 JDK8+. 如果使用JDK8, 版本需要在211以上. 下载, 解压 使用root用户 wget https://downloads.apac ...
- C++ 多线程的错误和如何避免(11)
不要在对时间敏感的上下文中使用 .get() 先看下面的代码, #include "stdafx.h" #include <future> #include <i ...
- win32 - QueryDisplayConfig的使用
QueryDisplayConfig函数检索关于所有显示设备的所有可能的显示路径,或视图,在当前设置的信息. C++样本: (开箱即用) 代码列出了所有显示器的名称和拓展模式 #include < ...
- 配置kube-apiserver基于token的认证机制
Kubernetes除了提供了基于CA证书的认证方式,也提供了基于HTTP Token的简单认证方式.各客户端组件与API Server之间的通信方式仍然采用HTTPS,但不采用CA数字证书.这种认证 ...
- nuxt调用weixin-js-sdk
在nuxt中调用weixin-js-sdk与在vue中有所不同. 通常在vue中用 import wx from 'weixin-js-sdk' 调用weixin-js-sdk,但在nuxt中会出现w ...
- collection.abc模块下的抽象基类UML类图说明
说明 Iterable.Container和Sized 每个容器都应该继承这三个抽象基类,或者实现兼容的协议.Iterable通过__iter__方法支持迭代, Container通过__contai ...
- 数据分析day02
案例 需求:双均线策略制定 1.使用tushare包获取某股票的历史行情数据 2.计算该股票历史数据的5日均线和30日均线 - 什么是均线? - 对于每一个交易日,都可以计算出前N天的移动平均值,然后 ...
- mysql数据库jar包下载
1.mysql-connector-java-8.0.16.jar驱动包 链接:https://pan.baidu.com/s/1G1SfPP895wU6YvTOAcTxhA提取码:7r43 2.my ...
