React+TypeScript手写拍照上传

前言

最近写了个招新面试系统,要求能支持拍照上传简历图片。经过对其义务,api的了解。用** React+TypeScript **手写出了一个原生的拍照上传组件,写此博客对此加以记录,也会公开到github方便日后的使用。

核心代码函数

1.调用摄像头

主要使用的是浏览器自带的api

typescript 复制代码
navigator.mediaDevices.getUserMedia

该api可以调用浏览器的摄像头权限,返回的是promise函数

typescript 复制代码
  mediaStream.getTracks().forEach((track) => {
    track.stop();
  });

具体代码:

typescript 复制代码
useEffect(() => {
  //组件挂载调用函数
  navigator.mediaDevices
    .getUserMedia({
      video: {
        width: 1080,
        height: 1920,
        frameRate: 30,
        facingMode: state.facing//调整摄像头的正反面
      },
      audio: false
    })
    .then((stream) => {
      mediaStream = stream;
      if (videoRef.current) {
        videoRef.current.srcObject = stream;
        videoRef.current.play();
      }
    })
    .catch((err) => {
      console.log(`An error occurred: ${err}`);
    });
  return () => {
    // 组件卸载时的逻辑
    if (mediaStream) {
      mediaStream.getTracks().forEach((track) => {
        track.stop();
      });
    }
  };
}, [state.facing]);

2.处理视频可播放事件

根据组件大小,计算视频流的宽高

typescript 复制代码
const handleCanPlay = () => {
  if (!state.streaming) {
    const height =
      (videoRef.current?.videoHeight as number) /
      (((videoRef.current?.videoWidth as number) / state.width) as number);
    setState({
      ...state,
      streaming: true,
      height: height as number
    });
  }
};

3.处理拍照事件

使用canvas来绘画视频流得出的照片结果

并调用onUploadPhoto回调函数将照片数据URL传递给父组件。

typescript 复制代码
const handleTakePhoto = () => {
  if (videoRef.current && canvasRef.current) {
    const { width, height } = state;
    canvasRef.current.width = width;
    canvasRef.current.height = height;
    const context = canvasRef.current.getContext('2d');
    if (context) {
      context.drawImage(videoRef.current, 0, 0, width, height);
      const photo = canvasRef.current.toDataURL('image/png');
      setState({ ...state, photo });
      onUploadPhoto(photo);
    }
  }
};

代码的封装

1.依赖引入

typescript 复制代码
import React, { useRef, useState, useEffect, ReactNode } from 'react';

2.定义组件接口类型:

typescript 复制代码
interface CameraProps {
  children?: ReactNode;
  onUploadPhoto: (photo: string) => void;//上传的函数
  onUnloadPhoto: (unload: boolean) => void;//卸载的函数
}

CameraProps接口定义了组件的属性类型。它包含了可选的children属性,以及onUploadPhoto和onUnloadPhoto两个回调函数属性。

3.定义组件状态

typescript 复制代码
interface CameraState {
  streaming: boolean;
  width: number;
  height: number;
  photo: string | undefined;
  facing: 'user' | 'environment';
}

CameraState接口定义了组件的状态类型。它包含了streaming表示是否正在录制、width和height表示视频的宽度和高度、photo表示拍摄的照片数据URL,以及facing表示相机的朝向。

4.定义相机组件:

typescript 复制代码
const Camera: React.FC<CameraProps> = ({ onUploadPhoto, onUnloadPhoto }) => {
  // ...
}

Camera是一个函数组件,接受CameraProps作为属性。组件内部使用了useState和useRef来创建状态和引用。

5.定义组件的状态和引用:

typescript 复制代码
const videoRef = useRef<HTMLVideoElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
let mediaStream: MediaStream | null = null;
const [state, setState] = useState<CameraState>({
  streaming: false,
  width: 320,
  height: 0,
  photo: undefined,
  facing: 'environment'
});

这段代码使用useRef创建了videoRef和canvasRef两个引用,分别指向视频元素和画布元素。mediaStream是一个变量用于存储媒体流对象。useState创建了state状态对象和对应的setState函数。

6.处理切换相机事件:

typescript 复制代码
const handleToggleFacing = () => {
  setState({ ...state, facing: state.facing === 'user' ? 'environment' : 'user' });
};

7.渲染组件:

typescript 复制代码
return (
  <div>
    <video ref={videoRef} onCanPlay={handleCanPlay} />
    <canvas ref={canvasRef} style={{ display: 'none' }} />
    {state.streaming && (
      <div>
        <button onClick={handleTakePhoto}>Take Photo</button>
        <button onClick={handleToggleFacing}>Toggle Camera</button>
      </div>
    )}
    {state.photo && <img src={state.photo} alt="Captured" />}
    {children}
  </div>
);

完整代码:

github

https://github.com/wzz778/aZeCamera

使用效果

手动马赛克🤣

拍照界面

上传界面

点击上传,调用handleUploadPhoto函数,返回图片的64编码

后言

功能pc,移动端都能使用,但样式主要适配了移动端,样式也可根据自己需求自行调整。

相关推荐
passerby606126 分钟前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角1 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!2 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰2 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi3 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen4 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马5 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒5 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端
乘风gg5 小时前
DeepSeek V4.1 Flash 来了,明天中午 Flash 降价 60%
前端·ai编程·claude
默_笙6 小时前
❗ 给金鱼装记忆(上):从白板到笔记本,AI 的短期记忆生存指南
前端·javascript