首页 > 其他分享 >记录--H5 实现拍照选景框效果

记录--H5 实现拍照选景框效果

时间:2024-01-25 18:46:08浏览次数:27  
标签:canvas const -- H5 width video getUserMedia 选景框 navigator

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

背景

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

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

 

标签:canvas,const,--,H5,width,video,getUserMedia,选景框,navigator
From: https://www.cnblogs.com/smileZAZ/p/17987904

相关文章

  • 层次分析法笔记【零基础数模系列】
    前言看了很多讲解新概念新模型的文章,这些文章往往要么讲的很浅不讲原理只讲应用,让人知其然不知其所以然。要么讲的很深小白看不懂,同时总是忽略关键部分,经常性引入陌生概念让初学者疑惑,因此有了本文,任何能熟练掌握线性代数知识且逻辑思维能力尚可的人都可以理解,而无需其他数模知识......
  • 收集Stream流的数据到集合或数组中
    1publicstaticvoidmain(String[]args){2List<String>list=newArrayList<>();3list.add("张三");4list.add("李四");5list.add("王武");6list.add("王武"......
  • 李宏毅《机器学习》总结 - CNN
    使用场景:对图片进行分类首先,将图片变成向量。例如,对于一个彩色的\(N\timesN\)(这个N指的是像素个数)图片,其对应着一个\(N\timesN\times3\)的矩阵(其中3是图片的channel,在彩色图片中,每个像素由RGB构成,因此channel为3)一个初始的想法将这个矩阵拉长,变成一个向量,然后......
  • go-zero配置DB的redis缓存
    配置定义:#catinternal/config/config.gopackageconfigimport( "github.com/zeromicro/go-zero/rest" "github.com/zeromicro/go-zero/core/stores/cache")typeConfigstruct{ rest.RestConf CacheRediscache.CacheConf}对应的配置文件:#cat......
  • python之生成器
    1.生成器优势节省空间:生成器按需生成值,避免了一次性加载所有数据到内存中。这对于处理大型数据集尤其重要。惰性计算:生成器支持惰性计算,只有在需要时才计算值。这在处理无限序列或需要动态生成数据的场景中非常有用。一次性使用:生成器通常是一次性的,一旦遍历完毕,就需要重新创......
  • 数据仓库oneID和主数据区别
    在了解oneID前,先来看一下阿里的oneDate体系。其实OneID在整个数据服务体系中,也只是起点不是终点或者说是手段,我们最终的目的是为了建设统一的数据资产体系。没有建设统一的数据资产体系之前,我们的数据体系建设存在下面诸多问题数据孤岛:各产品、业务的数据相互隔离,难以通过共性......
  • DOCKER 镜像创建
    DOCKER镜像创建 基于现有镜像创建 #创建个新的容器 #进入创建的容器里,下载epel源 #再下载个nginx #启动nginx #配置测试文件 #创建个新的镜像 #没有起来,基于本地的容器创建的镜像所创建的容器没有启动命令,要加上启动命令#根据新镜像创建个容器 #进......
  • 【每日GIS算法】(0)不同实体的构造
    本系列文章主要使用typescript手动实现GIS算法,其目的并不在于能够在正式生产中直接使用,而是可以通过对这些算法的实现,了解一些GIS方法的具体原理。本系列文章一定程度上与计算机图形学关系密切,也可以更好地了解图形学中相关知识点。本文作为本系列文章的第一篇,首先实现一些基础的......
  • 简单高效的语言
    php和python这两种语言,可以快速开发 python发送邮件#!/usr/bin/python#-*-coding:UTF-8-*-importsmtplibfromemail.mime.textimportMIMETextfromemail.headerimportHeadersender='[email protected]'receivers=['[email protected]']#接收邮件,......
  • 解决 fatal: unable to access 'https://github.com/alibaba/nacos.git/': Failed to
    直接打开这个网站:https://sites.ipaddress.com/github.com/。找到网站中的IP地址复制出里面的IPAddress并粘贴到hosts里面。以下是macos上使用命令行打开方式,也可以直接在硬盘上找到这个文件打开sudovim/private/etc/hosts在最后一行添加如下代码140.82.113.4githu......