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

React应用中摄像头视频流适配问题:避免过度缩放与留白

React摄像头视频流适配容器:无裁切无留白全设备兼容

你的问题本质是摄像头视频的宽高比与容器(视口)宽高比不匹配,导致cover裁切画面、contain产生留白。要实现视频自然显示、无缩放无留白的全设备兼容效果,可采用以下两种解决方案:


方案一:动态计算视频尺寸(无需修改摄像头流)

通过监听容器尺寸变化,结合视频原始宽高比,动态调整视频的宽高参数,使其刚好填满容器且保持比例无拉伸、无裁切。

代码实现

import { useRef, useEffect } from 'react';

function CameraVideo() {
  const videoRef = useRef(null);
  const containerRef = useRef(null);

  useEffect(() => {
    const video = videoRef.current;
    const container = containerRef.current;
    if (!video || !container) return;

    // 调整视频尺寸的核心逻辑
    const adjustVideoSize = () => {
      const videoRatio = video.videoWidth / video.videoHeight;
      const containerRatio = container.clientWidth / container.clientHeight;

      let newWidth, newHeight;
      if (containerRatio > videoRatio) {
        // 容器更宽:以容器高度为基准,按视频比例计算宽度
        newHeight = container.clientHeight;
        newWidth = newHeight * videoRatio;
      } else {
        // 容器更高:以容器宽度为基准,按视频比例计算高度
        newWidth = container.clientWidth;
        newHeight = newWidth / videoRatio;
      }

      video.style.width = `${newWidth}px`;
      video.style.height = `${newHeight}px`;
    };

    // 监听视频加载完成事件,获取原始宽高
    const handleVideoLoaded = () => adjustVideoSize();
    video.addEventListener('loadedmetadata', handleVideoLoaded);

    // 监听容器尺寸变化,实时调整视频
    const resizeObserver = new ResizeObserver(adjustVideoSize);
    resizeObserver.observe(container);

    // 请求摄像头流
    navigator.mediaDevices.getUserMedia({ video: true })
      .then(stream => video.srcObject = stream)
      .catch(err => console.error('摄像头请求失败:', err));

    // 清理资源
    return () => {
      resizeObserver.disconnect();
      video.removeEventListener('loadedmetadata', handleVideoLoaded);
      video.srcObject?.getTracks().forEach(track => track.stop());
    };
  }, []);

  return (
    <div 
      ref={containerRef}
      style={{
        position: 'relative',
        width: '100vw',
        height: '100vh',
        overflow: 'hidden',
      }}
    >
      <video
        ref={videoRef}
        style={{
          position: 'absolute',
          top: '50%',
          left: '50%',
          transform: 'translate(-50%, -50%) scaleX(-1)',
          objectFit: 'none', // 禁用默认适配逻辑,使用自定义计算
        }}
        autoPlay
        playsInline
        muted
      />
    </div>
  );
}

export default CameraVideo;

关键说明

  • 用ResizeObserver实时监听容器尺寸变化,适配横竖屏切换
  • 在loadedmetadata事件后获取视频原始宽高比,保证计算精度
  • 始终以容器的某一边为基准,按视频比例缩放另一维度,确保无留白、无拉伸

方案二:请求匹配容器宽高比的摄像头流

直接在请求摄像头时指定与容器宽高比一致的分辨率,让视频流本身的比例与容器完全匹配,此时使用objectFit: cover或contain都不会产生裁切或留白。

代码实现

import { useRef, useEffect } from 'react';

function CameraVideo() {
  const videoRef = useRef(null);
  const containerRef = useRef(null);

  useEffect(() => {
    const video = videoRef.current;
    const container = containerRef.current;
    if (!video || !container) return;

    // 请求匹配容器比例的摄像头流
    const getMatchedStream = async () => {
      const containerRatio = container.clientWidth / container.clientHeight;
      try {
        const stream = await navigator.mediaDevices.getUserMedia({
          video: {
            aspectRatio: { ideal: containerRatio }, // 指定匹配容器的宽高比
            width: { ideal: container.clientWidth },
            height: { ideal: container.clientHeight },
          },
        });
        video.srcObject = stream;
      } catch (err) {
        console.error('摄像头请求失败:', err);
      }
    };

    // 视口变化时重新请求适配的流
    const resizeObserver = new ResizeObserver(() => {
      video.srcObject?.getTracks().forEach(track => track.stop());
      getMatchedStream();
    });
    resizeObserver.observe(container);

    getMatchedStream();

    // 清理资源
    return () => {
      resizeObserver.disconnect();
      video.srcObject?.getTracks().forEach(track => track.stop());
    };
  }, []);

  return (
    <div 
      ref={containerRef}
      style={{
        position: 'relative',
        width: '100vw',
        height: '100vh',
        overflow: 'hidden',
      }}
    >
      <video
        ref={videoRef}
        style={{
          position: 'absolute',
          top: '50%',
          left: '50%',
          width: '100%',
          height: '100%',
          transform: 'translate(-50%, -50%) scaleX(-1)',
          objectFit: 'cover', // 比例匹配,不会裁切
        }}
        autoPlay
        playsInline
        muted
      />
    </div>
  );
}

export default CameraVideo;

关键说明

  • 通过aspectRatio约束摄像头输出的比例,从根源解决适配问题
  • 视口变化时重新请求流,保证始终匹配当前容器比例
  • 该方案性能更优,无需额外的前端尺寸计算

注意事项

  • 部分旧设备可能不支持aspectRatio约束,此时方案一兼容性更好
  • 若配合Three.js场景使用,需确保容器尺寸与Three.js画布尺寸一致,避免画面错位
  • 两种方案均支持横竖屏自动适配,可根据项目需求选择

内容的提问来源于stack exchange,提问作者Zephyr Sonic

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 12:34:57