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
相关产品推荐
相关产品推荐

