摄像头以及扫一扫

前言

  • 在写 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年难得一遇的什么?当然是疯狂星期四

相关推荐
栈老师不回家5 分钟前
Vue 计算属性和监听器
前端·javascript·vue.js
前端啊龙10 分钟前
用vue3封装丶高仿element-plus里面的日期联级选择器,日期选择器
前端·javascript·vue.js
一颗松鼠14 分钟前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
小远yyds35 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
吕彬-前端1 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱2 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai2 小时前
uniapp
前端·javascript·vue.js·uni-app
bysking3 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓3 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4113 小时前
无网络安装ionic和运行
前端·npm