摄像头以及扫一扫

前言

  • 在写 JS30 19 - Webcam Fun 时发现是个拍照并保存的需求,之前写项目时也遇到了这方面内容,并且当时还需要生成和扫描二维码(扫描是 App 带有的),这部分当时因为一些原因未自己动手,现在遇到了正好了解并学习拓展一下。
  • BTW,这作者当时咋想的做了个这样示例,挺抽象的,昨天刚觉得他设计天赋🐂呢

正文

摄像头

获取摄像头权限

通过 Navigator.mediaDevices 只读属性返回一个 MediaDevices 对象,该对象可提供对相机和麦克风等媒体输入设备以及屏幕共享的连接访问。

  • 本文中主要用到 enumerateDevices 查询是否有媒体设备以及 getUserMedia 方法实现对 audio 以及 video 权限的获取
  • 至于屏幕共享为getDisplayMedia 方法,暂未尝试

查询媒体设备

  • 打印出来是个数组,里面有各个媒体设备的信息
js 复制代码
async function checkHasDevices(mediaDevices) {
    try {
        const devices = await mediaDevices.enumerateDevices();
        return devices
    } catch (err) {
        console.log(err.name + ": " + err.message);
        return []
    }
}

申请权限

  • 有了媒体设备,使用 getUserMedia 函数即可,这个函数的参数是一个对象,里面包括对 audio 以及 video 详细的配置
  • 再就是注意下是 video.srcObject ,因为 stream 是二进制流的形式
js 复制代码
const video = document.querySelector('.player');
async function getVideoPermission() {
    const mediaDevices = navigator.mediaDevices;
    const devices = await checkHasDevices(mediaDevices); 
    console.log(devices);
    if (devices.length > 0) {
        const constraints = {
            audio: false,
            video: {
                width: 1920,              //分辨率,ideal 意为理想情况
                // { min: 1024, ideal: 1280, max: 1920 }, 
                height: 1080,             //分辨率,ideal 意为理想情况
                //  { min: 776, ideal: 720, max: 1080 },
                facingMode: "user"        // 前置
                //  facingMode:  {exact:"environment" } //后置,exact 为强制
            }
          }
        try {
            const stream = await mediaDevices.getUserMedia(constraints);
            video.srcObject = stream;
            video.play();
        } catch (err) {
            console.log(err);
        }
    } else {
        console.log("No devices found.");
    }
}
  • 发现笔记本设置 facingMode 无论为哪个值都是调前置,如果使用强制后置摄像头的话就会抛出 OverconstrainedError了🤨

截屏

好!现在已经踏出了第一步!接下来进行截屏操作!

  • 截屏其实很简单,因为视频是一帧一帧的嘛,截屏就是拿到某一帧然后展示,在此之前需要先了解下 Canvas 的基本用法
js 复制代码
function screenshot() {
    const width = video.videoWidth;
    const height = video.videoHeight;
    canvas.width = width;
    canvas.height = height;
    ctx.drawImage(video, 0, 0, width, height);
}
  • 要下载的话就加个 a 标签自动触发下就行
js 复制代码
function screenshot() {
    const width = video.videoWidth;
    const height = video.videoHeight;
    canvas.width = width;
    canvas.height = height;
    ctx.drawImage(video, 0, 0, width, height);
    const data = canvas.toDataURL();// 参数可指定图片类型
    const link = document.createElement('a');
    link.href = data;
    link.setAttribute('download', 'handsome');
    link.click()
}

扫一扫

简单认识

  • 二维码其实就是一个 Url 地址以一种形式存放到我们常见的那种图像里,通过识别可以得到其中的地址
  • 可以在 Antd 的组件 QRCode试试
  • 使用的扫一扫为社区找到的一个项目 qr-sanner-wechat

识别

  • 因为浏览器不支持模块化,用原生 JS 写的话还得再用打包工具打包,得配置一堆东西,因此使用 vite 创了个简单的 Vanilla + TS项目来展示
ts 复制代码
// main.ts
import { scanner } from "./counter"

document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
        <input type="file" accept="image/png" />
        <img src=" " alt="a" width='200' height='200' />
`
scanner(
        document.querySelector<HTMLInputElement>('input')!,
        document.querySelector<HTMLImageElement>('img')!
)
ts 复制代码
//counter.ts
import { scan } from 'qr-scanner-wechat'

export function scanner(
    input:HTMLInputElement,
    img:HTMLImageElement,
    container:HTMLDivElement
) {
    input!.addEventListener('change',async (e:Event)=>{
    const fileElement = (e.target as HTMLInputElement)
    if(!fileElement.files?.length) return
    const file = fileElement.files[0]
    const value = URL.createObjectURL(file)
    img!.src = value
    img.addEventListener('load', async () => {
      await coverQRCode(img, container);
  })
})
}
  • 我们可以发现打印出来的信息是这样
    • rect : 即rectangle,他告诉了你这个图片的一个相对位置以及宽高
    • text : 即这张图片对应的 Url 地址

有了 text,我们就可以轻松跳转到对应地址,完成对二维码的识别

拓宽思路

问题所在

  • 很多扫一扫都是在有多个二维码时识别多个,然后让用户选择

  • 试试这个图像如果直接丢给第三方库会如何

    妈耶,🐓了,只有一个

    那怎么办?

只要思想不滑坡,方法总比困难多

  • 使用其它库
  • 不能一起识别,就一个一个识别,识别一个遮住一个

注意到该库的的 scan 参数也可以是 HTMLCanvasElement 类型,因此可以使用 canvas 结合 result 中的 rect 属性来生成遮挡图形遮住已经扫描的二维码

ts 复制代码
async function coverQRCode(img: HTMLImageElement, container: HTMLDivElement) {
  const canvas = document.createElement('canvas');
  canvas.width = img.width;
  canvas.height = img.height;
  const ctx = canvas.getContext('2d');
  ctx?.drawImage(img, 0, 0, img.width, img.height);

  let continueScanning = true;

  while (continueScanning) {
      const res = await scan(canvas) as any
      if (!res.text) {
          continueScanning = false;
          break;
      }

      scannedQRCodeInfo.push(res);
      const { x, y, width, height } = res.rect;
      ctx!.fillStyle = 'black';
      ctx?.fillRect(x, y, width, height);

      const codeCanvas = document.createElement('canvas');
      codeCanvas.width = width;
      codeCanvas.height = height;
      const codeCtx = codeCanvas.getContext('2d');
      codeCtx?.drawImage(canvas, x, y, width, height, 0, 0, width, height);
      container.appendChild(codeCanvas);
  }
}

芜湖!成了!

供用户选择跳转

都做到这一步了,那后面的岂不是🤲🏻🤲🏻🤲🏻🤲 易如反掌 易如反掌

  • 可以加一个数组存放所有扫描得到的信息,方便在每一个二维码上显示选项
  • 刚才是为了演示用黑块遮住了二维码,现在根据坐标信息在每个二维码上绘制一个小绿圈就行
ts 复制代码
function draw(container:HTMLDivElement) {
  scannedQRCodeInfo.forEach(({rect, text}:any) => {
  const dom = document.createElement("div");
  const { x, y, width, height } = rect;
  const _x = (x || 0) + width / 2 - 20;
  const _y = (y || 0) + height / 2 - 20;
  dom.style.width = "40px";
  dom.style.height = "40px";
  dom.style.background = "green";
  dom.style.position = "absolute";
  dom.style.zIndex = "9";
  dom.style.top = _y + "px";
  dom.style.left = _x + "px";
  dom.style.color = "#fff";
  dom.style.textAlign = "center";
  dom.style.borderRadius = "100px";
  dom.style.borderBlockColor = "#fff";
  dom.style.borderColor = "unset";
  dom.style.borderRightStyle = "solid";
  dom.style.borderWidth = "3px";
  dom.addEventListener("click", () => {
    console.log(text);
  });
  container.appendChild(dom);
});
}

最终效果

源码

放在了 Github 里,有兴趣可以拉下来玩玩 scanCode

结语

钻研这些东西还挺好玩的,这篇文章也是花费了几天的时间才写出来,中间甚至经历了28年难得一遇的什么?当然是疯狂星期四

相关推荐
学习使我快乐0126 分钟前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio199527 分钟前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
黄尚圈圈1 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水2 小时前
简洁之道 - React Hook Form
前端
正小安4 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch6 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光6 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   6 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   6 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web6 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery