You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React实时视频识别代码转Angular组件的步骤及处理咨询

React实时视频识别功能迁移到Angular的完整步骤

首先明确:不能直接将JSX文件改扩展名或复制代码到Angular TS组件中。React和Angular的组件模型、生命周期、模板语法完全不同,必须按Angular的架构重构代码。


一、前置依赖安装

在Angular项目根目录执行命令,安装Tesseract.js及类型定义:

npm install tesseract.js
npm install @types/tesseract.js --save-dev

二、生成Angular组件

创建专门的视频扫描组件:

ng generate component video-scanner

三、重构核心功能

1. 模板文件(video-scanner.component.html)

将React的JSX转为Angular模板语法,使用属性绑定处理样式和元素引用:

<div>
  <video
    #myVideo
    autoplay
    muted
    playsinline
    [style.width.px]="vidWidth"
    [style.height.px]="vidHeight"
    [style.position]="'absolute'"
    [style.top.px]="vidOffsetTop"
    [style.left.px]="vidOffsetLeft"
    [style.zIndex]="2"
  ></video>
  <div
    [style.width.px]="indWidth"
    [style.height.px]="indHeight"
    [style.border]="'1px solid red'"
    [style.zIndex]="3"
    [style.position]="'absolute'"
    [style.top.px]="indOffsetTop"
    [style.left.px]="indOffsetLeft"
  ></div>
</div>

2. 组件类文件(video-scanner.component.ts)

用Angular的生命周期钩子、@ViewChild替代React的useEffect和useRef,重构业务逻辑:

import { Component, ViewChild, ElementRef, OnInit, OnDestroy, Output, EventEmitter } from '@angular/core';
import { createWorker } from 'tesseract.js';

@Component({
  selector: 'app-video-scanner',
  templateUrl: './video-scanner.component.html',
  styleUrls: ['./video-scanner.component.css']
})
export class VideoScannerComponent implements OnInit, OnDestroy {
  @ViewChild('myVideo') myVideo!: ElementRef<HTMLVideoElement>;
  @Output() codeChange = new EventEmitter<string>();
  @Output() scanningChange = new EventEmitter<boolean>();

  private myStream?: MediaStream;
  private scannedCodes: Record<string, boolean> = {};
  private worker = createWorker();
  private animationId?: number;

  // 视频配置参数
  vidWidth = window.innerWidth - 60;
  vidHeight = 260;
  vidOffsetTop = 240;
  vidOffsetLeft = (window.innerWidth / 3) - (this.vidWidth / 2);

  // 识别框配置参数
  marginX = 40;
  indWidth = this.vidWidth - this.marginX;
  indHeight = 80;
  indOffsetTop = this.vidOffsetTop + (this.vidHeight / 2) - (this.indHeight / 2);
  indOffsetLeft = (window.innerWidth / 2) - (this.indWidth / 2);

  ngOnInit(): void {
    this.initCameraAndOCR();
  }

  ngOnDestroy(): void {
    // 清理资源,避免内存泄漏
    if (this.myStream) {
      this.myStream.getTracks().forEach(track => track.stop());
    }
    if (this.animationId) {
      cancelAnimationFrame(this.animationId);
    }
    this.worker.terminate();
  }

  private async initCameraAndOCR(): Promise<void> {
    try {
      this.scanningChange.emit(true);
      // 请求摄像头权限
      const stream = await navigator.mediaDevices.getUserMedia({
        video: { facingMode: 'environment' },
        audio: false
      });
      // 绑定视频流
      const videoElement = this.myVideo.nativeElement;
      videoElement.srcObject = stream;
      await videoElement.play();
      
      this.myStream = stream;
      this.scannedCodes = {};

      // 初始化Tesseract OCR worker
      await this.worker.load();
      await this.worker.loadLanguage('eng');
      await this.worker.initialize('eng');

      // 启动帧识别循环
      this.tick();
    } catch (err) {
      console.error(err);
      this.scanningChange.emit(false);
      // 这里可添加错误提示逻辑,比如弹窗
    }
  }

  private async tick(): Promise<void> {
    const videoElement = this.myVideo.nativeElement;
    if (videoElement.readyState === videoElement.HAVE_ENOUGH_DATA) {
      // 创建Canvas处理视频帧
      const canvas = document.createElement('canvas');
      canvas.width = this.indWidth;
      canvas.height = this.indHeight;

      const ctx = canvas.getContext('2d');
      if (ctx) {
        // 截取视频中指定区域绘制到Canvas
        const sx = (this.marginX / 2) / 2;
        const sy = this.vidHeight - this.indHeight;
        const sWidth = this.indWidth * 2;
        const sHeight = this.indHeight * 2;
        const dx = 0;
        const dy = 0;
        const dWidth = this.indWidth;
        const dHeight = this.indHeight;

        ctx.drawImage(videoElement, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
      }

      // OCR识别
      const { data: { text } } = await this.worker.recognize(canvas);
      const regex = /[a-zA-Z0-9]/gi;
      const scannedText = text?.match(regex)?.filter(x => x).join('') || '';
      
      if (scannedText) {
        this.codeChange.emit(scannedText);
      }
      console.log({ text, scannedText });
    }
    // 循环处理下一帧
    this.animationId = requestAnimationFrame(() => this.tick());
  }
}

3. 样式文件(video-scanner.component.css)

可留空或补充全局样式,模板中已通过绑定实现了原React的内联样式逻辑。

四、父组件使用

在父组件模板中引入扫描组件,并绑定回调事件:

<app-video-scanner
  (codeChange)="handleCodeUpdate($event)"
  (scanningChange)="handleScanningStatus($event)"
></app-video-scanner>

在父组件类中实现对应的处理方法:

handleCodeUpdate(code: string): void {
  // 处理识别到的代码,对应React的setCode
}

handleScanningStatus(isScanning: boolean): void {
  // 处理扫描状态,对应React的setScanning
}

内容的提问来源于stack exchange,提问作者Ziad El Khomssi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 05:45:33