摄像头以及扫一扫

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

正文 摄像头 获取摄像头权限

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

  • 本文中主要用到%20enumerateDevices%20查询是否有媒体设备以及%20getUserMedia%20方法实现对%20audio%20以及%20video%20权限的获取
  • 至于屏幕共享为getDisplayMedia%20方法,暂未尝试
查询媒体设备
  • 打印出来是个数组,里面有各个媒体设备的信息
async%20function%20checkHasDevices(mediaDevices)%20{
%20%20%20%20try%20{
%20%20%20%20%20%20%20%20const%20devices%20=%20await%20mediaDevices.enumerateDevices();
%20%20%20%20%20%20%20%20return%20devices
%20%20%20%20}%20catch%20(err)%20{
%20%20%20%20%20%20%20%20console.log(err.name%20+%20":%20"%20+%20err.message);
%20%20%20%20%20%20%20%20return%20[]
%20%20%20%20}
}

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

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

  • 截屏其实很简单,因为视频是一帧一帧的嘛,截屏就是拿到某一帧然后展示,在此之前需要先了解下%20Canvas%20的基本用法
function%20screenshot()%20{
%20%20%20%20const%20width%20=%20video.videoWidth;
%20%20%20%20const%20height%20=%20video.videoHeight;
%20%20%20%20canvas.width%20=%20width;
%20%20%20%20canvas.height%20=%20height;
%20%20%20%20ctx.drawImage(video,%200,%200,%20width,%20height);
}
  • 要下载的话就加个%20a%20标签自动触发下就行
function%20screenshot()%20{
%20%20%20%20const%20width%20=%20video.videoWidth;
%20%20%20%20const%20height%20=%20video.videoHeight;
%20%20%20%20canvas.width%20=%20width;
%20%20%20%20canvas.height%20=%20height;
%20%20%20%20ctx.drawImage(video,%200,%200,%20width,%20height);
%20%20%20%20const%20data%20=%20canvas.toDataURL();//%20参数可指定图片类型
%20%20%20%20const%20link%20=%20document.createElement('a');
%20%20%20%20link.href%20=%20data;
%20%20%20%20link.setAttribute('download',%20'handsome');
%20%20%20%20link.click()
}
扫一扫 简单认识
  • 二维码其实就是一个%20Url%20地址以一种形式存放到我们常见的那种图像里,通过识别可以得到其中的地址
  • 可以在%20Antd%20的组件%20QRCode试试
  • 使用的扫一扫为社区找到的一个项目%20qr-sanner-wechat
识别
  • 因为浏览器不支持模块化,用原生%20JS%20写的话还得再用打包工具打包,得配置一堆东西,因此使用%20vite%20创了个简单的%20Vanilla%20+%20TS项目来展示
//%20main.ts
import%20{%20scanner%20}%20from%20"./counter"

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

export%20function%20scanner(
%20%20%20%20input:HTMLInputElement,
%20%20%20%20img:HTMLImageElement,
%20%20%20%20container:HTMLDivElement
)%20{
%20%20%20%20input!.addEventListener('change',async%20(e:Event)=>{
%20%20%20%20const%20fileElement%20=%20(e.target%20as%20HTMLInputElement)
%20%20%20%20if(!fileElement.files?.length)%20return
%20%20%20%20const%20file%20=%20fileElement.files[0]
%20%20%20%20const%20value%20=%20URL.createObjectURL(file)
%20%20%20%20img!.src%20=%20value
%20%20%20%20img.addEventListener('load',%20async%20()%20=>%20{
%20%20%20%20%20%20await%20coverQRCode(img,%20container);
%20%20})
})
}
  • 我们可以发现打印出来的信息是这样

    • rect%20:%20即rectangle,他告诉了你这个图片的一个相对位置以及宽高
    • text%20:%20即这张图片对应的%20Url%20地址

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

拓宽思路 问题所在
  • 很多扫一扫都是在有多个二维码时识别多个,然后让用户选择

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

    妈耶,🐓了,只有一个

    %20那怎么办?

只要思想不滑坡,方法总比困难多
  • 使用其它库
  • 不能一起识别,就一个一个识别,识别一个遮住一个

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

async%20function%20coverQRCode(img:%20HTMLImageElement,%20container:%20HTMLDivElement)%20{
%20%20const%20canvas%20=%20document.createElement('canvas');
%20%20canvas.width%20=%20img.width;
%20%20canvas.height%20=%20img.height;
%20%20const%20ctx%20=%20canvas.getContext('2d');
%20%20ctx?.drawImage(img,%200,%200,%20img.width,%20img.height);

%20%20let%20continueScanning%20=%20true;

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

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

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

芜湖!成了!

供用户选择跳转

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

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

源码

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

结语

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

原文链接:https://juejin.cn/post/7341267497427353600%20作者:satrr

(0)
上一篇 2024年3月2日 下午5:08
下一篇 2024年3月3日 上午10:01

相关推荐

发表回复

登录后才能评论