首页/文章/移动端web

H5 实现拍照选景框效果利用

2024-09-04
16436 分钟
...

背景

在实际项目中,遇到了需要唤起手机摄像头拍照的需求,最开始是通过<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/…


if (navigator.mediaDevices === undefined) {
  navigator.mediaDevices = {};
}



if (navigator.mediaDevices.getUserMedia === undefined) {
  navigator.mediaDevices.getUserMedia = function (constraints) {
    
    var getUserMedia =
      navigator.webkitGetUserMedia || navigator.mozGetUserMedia;

    
    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);
    });
  };
}

navigator.mediaDevices
  .getUserMedia({ audio: true, video: true })
  .then(function (stream) {
    var video = document.querySelector("video");
    
    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.stop()



 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 相关的参数,以及裁切绘制后的图片是否可以被业务方所识别的问题,如果参数调整的不合适,会出现图片变形,模糊的情况

参考文档

案例参考:

1、juejin.cn/post/697031…

2、www.cnblogs.com/yuzhihui/p/…

3、github.com/ericlee33/h…

MDN API 参考:

1、CanvasRenderingContext2D.drawImage():developer.mozilla.org/zh-CN/docs/…

2、MediaDevices.getUserMedia():developer.mozilla.org/zh-CN/docs/…

其他:

1、如何让Chrome浏览器允许http网站打开摄像头和麦克风:juejin.cn/post/699030…

浏览知识共享许可协议

本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
移动端web2024-09-04
封装一个H5照相机组件(放大缩小、连拍、预览、手电筒,横竖屏拍摄)
项目上有个需求就是说移动端网页中的照相机要支持连拍功能。 要知道咱们一般网页中通过input标签调用出来的照相机都是拍完后需要进行点击确认操作,然后再重新点击拍照按钮调起照相机如此往复,这样的交互方式对于需要快速拍摄照片的场景来说效率确实太低了。 如果本机摄像头无法进行连拍操作的话只能通过直接调用媒体摄像头的方式看看能否实现,考虑到开源的组件UI和交互方式可能无法满足项目需要且也不太好扩展功能所以觉得自己封装一个。 使用到的技术 Nav...
学习
移动端web2024-09-03
H5移动端-解决点击输入框时页面自动放大的问题
问题 在移动端项目,我们常遇到的一个问题:用户点击输入框时,页面会不期望地发生自动缩放,这既打断了用户的输入流程,也影响了整体的用户体验。 我们的目标是优化这一行为,确保用户在点击输入框时,页面能够保持稳定,仅允许用户专注于内容的输入,从而提升产品的可用性和满意度。 解决前后效果对比 方案 针对移动端点击输入框导致页面自动缩放的问题,我们需要根据不同操作系统(主要是安卓与iOS)采取差异化的解决方案。 安卓:通常可以通过在HTML文档头...
学习
移动端web2024-03-12
h5调用手机摄像头踩坑
一般业务也很少接触摄像头,有也是现成的工具库扫个二维码。难得用一次,记录下踩坑。 2.1. 这个就不用多说了,缺点就是没办法自定义界面,它是调用的系统原生相机界面。 2.2. 由于我需要自定义界面,就像下面这样: 所以我选择了这个方案,这个 使用起来其实很简单: 可以看到这个效果。 如果你复制我的代码,在 上肯定能运行,但是你想在手机上试试的时候就会发现很多问题。 3.1. 需要 由于浏览器的安全设置,除了 和 连接,你都没办法获取到...
学习
移动端web2024-03-12
探秘移动端网页调用摄像头的两种方式
简介: PC 端网页调用摄像头的场景想必大家并不陌生,打开一个网址,开启摄像头开始笔试/视频聊天/直播等。而在移动端网页调用摄像头的场景你见得多吗?我想答案应该是不多吧(在下见识浅薄)。 前言小叙 PC 端网页调用摄像头 的场景想必大家并不陌生,打开一个网址,开启摄像头开始笔试/视频聊天/直播等。 而在 移动端网页调用摄像头 的场景你见得多吗?我想答案应该是不多吧(在下见识浅薄)。 H5 相较于native app 一直被诟病的就有 调...
学习
移动端web2023-12-06
PC调试手机微信浏览器
此方案,可以调试一些仅可在微信客户端才能打开的网页,以及一些需要微信授权的网页,都可以用此方式进行真机调试,可快速分析和逆向 准备工具 一部手机 一台电脑 一条数据线 首先用数据线把手机与电脑连接 然后手机进入到开发者模式并且要是开启状态,具体进入方式,根据机型不同,进入方式不同,自行百度。 进入到开发者选项之后,开启USB调试模式。 然后在手机微信浏览器随便进入一个网站,例如: 再回到电脑浏览器输入: chrome内核的浏览器输入 c...
学习
AI2026-09-01
Deep Agents 01:何为 Agent Harness,以及如何开始
1、本篇任务:完成一份多步骤、带证据的技术调研 普通客服 Agent 的问题短、工具少、输出即时。技术调研或编码任务会持续很久,产生计划、搜索结果、文件和中间结论。Deep Agents 在 LangChain/LangGraph 之上预装规划、虚拟文件系统、上下文压缩和子 Agent,适合这类开放任务。 本课让 Agent 比较两种向量数据库,并交付一份可验证报告。 2、什么时候需要 Deep Agent 满足以下两项以上再考虑:任务...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录