找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1955

积分

0

好友

272

主题
发表于 2025-12-31 04:07:00 | 查看: 22| 回复: 0

最近接到一个非常有意思的需求,需要在前端实现一个仿 微信扫一扫 的功能。这其中,调用摄像头并实现拍照是核心基础。本文将详细讲解如何利用现代Web API在Vue3项目中实现这一功能。

一、检查设备:window.navigator

想要调用设备的摄像头,首先需要确认当前环境是否支持。浏览器全局对象 window 身上有一个 navigator 属性,它的 mediaDevices 属性是我们实现功能的关键。

我们可以先设计一个 checkCamera 函数,在页面初始化时执行,用于检查媒体设备。
检查摄像头设备代码
图1:用于检查摄像头设备的代码片段

在控制台查看这个对象,你可能会发现它只有一个值为 nullondevicechange 属性。别担心,真正有用的方法在其原型(__proto__)上。
MediaDevices对象属性
图2:浏览器控制台中的MediaDevices对象

展开其原型属性,请注意 enumerateDevicesgetUserMedia 这两个方法,它们是我们本章节的主角。
MediaDevices原型方法
图3:MediaDevices原型上的关键方法

在这一步,我们只需用 enumerateDevices 函数检查当前有哪些媒体设备。该函数返回一个 Promise,我们可以用 async/await 简化代码。
enumerateDevices函数调用
图4:调用enumerateDevices函数的异步代码
设备列表输出
图5:控制台输出的音频、视频输入设备列表

如上图所示,设备列表中包含 videoinput 类型的设备,这证明当前环境有可用的摄像头,我们可以继续进行下一步。

二、获取摄像头流数据

接下来,我们将使用 navigator.mediaDevices.getUserMedia() 这个核心API。它接收一个配置对象作为参数,我们可以预设视频的宽度、高度以及摄像头朝向。

这里我们重点关注 facingMode 属性。对于扫一扫功能,通常需要使用后置摄像头(environment),而前置摄像头则为 user
getUserMedia参数配置
图6:调用getUserMedia时传入的配置参数

当执行这个函数后,浏览器会弹出权限请求对话框。
摄像头权限请求
图7:浏览器请求使用摄像头的权限提示

点击“允许”后,页面可能没有任何变化。这是因为 getUserMedia 仅仅返回了一个媒体流(MediaStream) 对象。可以这样理解:浏览器得到了使用摄像头的许可和原始数据流,但还不知道该把画面显示在哪里。

我们需要一个“显示器”来承载这个数据流,而原生的 <video> 标签正是最佳选择。

在Vue模板中创建一个 <video> 标签,并通过 ref 获取其DOM元素。
创建video元素
图8:在Vue模板中创建video元素并绑定ref

关键一步在于:将 getUserMedia 返回的流数据赋值给 video 元素的 srcObject 属性。这就相当于把数据线插到了显示器上。
注意:video.srcObject,而非 video.src
将流数据赋值给video
图9:获取流数据并赋值给video.srcObject

现在,你应该能在页面上看到摄像头捕获的实时画面了。
视频播放效果
图10:摄像头画面成功在video标签中播放

三、截取当前画面(拍照)

我们添加一个按钮作为拍摄键,目标是点击时截取当前视频画面。
拍摄按钮界面
图11:包含拍摄按钮的界面

这里需要理解一个原理:尽管视频看起来是连续的,但浏览器渲染时其实是一帧一帧进行的。打开浏览器的 Performance 面板录制页面加载过程,可以看到渲染过程是由无数帧拼接而成的。
Performance面板帧渲染
图12:Performance面板展示的逐帧渲染过程

同理,摄像头视频流也是由连续的帧构成。因此,拍照的本质就是:在按下按钮的瞬间,获取 video 标签当前显示的那一帧画面并保存

实现这个功能,我们需要借助 <canvas> 的能力。别担心,这里只会用到其基础功能。

首先,动态创建一个 <canvas> 元素,并将其宽高设置为与 video 元素的视频宽高一致。
创建等大canvas元素
图13:创建与video等尺寸的canvas元素

接下来是重点:调用 canvas 的 getContext(‘2d’) 方法,获取一个2D渲染上下文对象。你可以简单理解为,这个方法为 canvas “激活”了绘图能力,并返回一个包含各种绘图方法的工具对象。
getContext方法说明
图14:Canvas API中getContext方法的文档说明

在这个上下文对象 ctx 身上,我们只需使用 drawImage 这一个方法。
drawImage方法说明
图15:CanvasRenderingContext2D.drawImage()方法说明

drawImage 有多种重载,我们使用五参数的版本:drawImage(image, dx, dy, dWidth, dHeight)
drawImage五参数语法
图16:drawImage方法的五参数用法

  • dx, dy:指在画布上放置图像的起点坐标(左上角),类似于设置 margin-leftmargin-top
    画布坐标系示意图
    图17:画布坐标系及dx,dy参数示意
  • dWidth, dHeight:指在画布上绘制图像的宽度和高度。
  • image:可以是多种图像源,包括 <video> 元素。当传入 <video> 时,它会自动捕获该元素的当前帧。

所以,完整的拍摄函数 shoot 如下:

function shoot() {
  if (!videoEl.value || !wrapper.value) return;
  const canvas = document.createElement("canvas");
  canvas.width = videoEl.value.videoWidth;
  canvas.height = videoEl.value.videoHeight;
  //拿到 canvas 上下文对象
  const ctx = canvas.getContext("2d");
      ctx?.drawImage(videoEl.value, 0, 0, canvas.width, canvas.height);
  wrapper.value.appendChild(canvas);//将 canvas 投到页面上
}

现在来测试一下拍照效果。
拍照功能演示
图18:点击按钮,成功将当前帧画面绘制到canvas并显示

四、完整示例代码

以下是整合了上述所有步骤的 Vue 3 单文件组件示例代码:

<script lang="ts" setup>
import { ref, onMounted } from "vue";

const wrapper = ref<HTMLDivElement>();
const videoEl = ref<HTMLVideoElement>();

async function checkCamera() {
  const navigator = window.navigator.mediaDevices;
  const devices = await navigator.enumerateDevices();
  if (devices) {
    const stream = await navigator.getUserMedia({
      audio: false, // 我们不需要音频
      video: {
        width: 300,
        height: 300,
        // facingMode: { exact: "environment" }, //强制后置摄像头
        facingMode: "user", //前置摄像头
      },
    });
    if (!videoEl.value) return;

    videoEl.value.srcObject = stream;
    videoEl.value.play();
  }
}

function shoot() {
  if (!videoEl.value || !wrapper.value) return;
  const canvas = document.createElement("canvas");
  canvas.width = videoEl.value.videoWidth;
  canvas.height = videoEl.value.videoHeight;
  //拿到 canvas 上下文对象
  const ctx = canvas.getContext("2d");
  ctx?.drawImage(videoEl.value, 0, 0, canvas.width, canvas.height);
  wrapper.value.appendChild(canvas);
}

onMounted(() => {
  checkCamera();
});
</script>

<template>
  <div ref="wrapper" class="w-full h-full bg-red flex flex-col items-center">
    <video ref="videoEl" />
    <div
      @click="shoot"
      class="w-100px leading-100px text-center bg-black text-30px"
    >
      拍摄
    </div>
  </div>
</template>

五、总结

实现前端拍照的整体思路非常清晰:首先通过 WebRTC 的 getUserMedia API 获取摄像头媒体流,并将其绑定到 <video> 标签进行实时预览。当需要拍照时,利用 <canvas>getContext(‘2d’) 获取绘图上下文,再使用 drawImage 方法捕获 <video> 标签的当前帧并绘制到画布上,从而得到静态图片。

这仅仅是仿扫一扫功能的第一步。在此基础上,你可以进一步处理 canvas 上的图像数据,例如使用诸如 jsQR 等库进行二维码识别,或者将图片数据上传至服务器。希望这篇在 云栈社区 分享的教程能帮助你理解前端操作摄像头的核心原理。




上一篇:程序员亲历:软件公司裁员倒闭前的七个危险征兆
下一篇:掌握这10个高级SQL查询技巧,轻松应对数据分析与面试挑战
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 18:32 , Processed in 0.306102 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表