为什么要封装H5照相机组件?
移动端网页中通过<input type="file">
调用的原生相机体验较差:
- 每次拍照后需要确认操作
- 无法快速连续拍摄
- 缺少放大缩小、手电筒等常用功能
封装自定义相机组件可以:
- 实现连拍功能,提高拍摄效率
- 添加更多相机功能
- 统一UI风格
核心实现步骤
1. 显示摄像头画面
javascript
// 获取摄像头权限
const getStream = async () => {
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
facingMode: "environment", // 后置摄像头
width: 1920,
height: 1440
}
});
// 将视频流绑定到video元素
videoRef.current.srcObject = stream;
videoRef.current.play();
};
2. 拍照功能实现
javascript
const takePhoto = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 设置画布大小
canvas.width = 480;
canvas.height = 640;
// 从video元素捕获画面
ctx.drawImage(
videoRef.current,
0, 0, videoWidth, videoHeight, // 源图像参数
0, 0, canvas.width, canvas.height // 目标画布参数
);
// 转换为图片
const photoData = canvas.toDataURL('image/jpeg');
return photoData;
};
3. 连拍功能
javascript
const [photos, setPhotos] = useState([]);
const continuousShooting = () => {
const timer = setInterval(() => {
const newPhoto = takePhoto();
setPhotos(prev => [...prev, newPhoto]);
}, 1000); // 每秒拍一张
// 5秒后停止
setTimeout(() => clearInterval(timer), 5000);
};
4. 放大缩小功能
javascript
const [zoom, setZoom] = useState(1);
// 放大
const zoomIn = () => {
if (zoom >= 4) return;
setZoom(zoom + 0.2);
videoRef.current.style.transform = `scale(${zoom + 0.2})`;
};
// 缩小
const zoomOut = () => {
if (zoom <= 1) return;
setZoom(zoom - 0.2);
videoRef.current.style.transform = `scale(${zoom - 0.2})`;
};
5. 手电筒功能
javascript
const toggleFlash = () => {
const track = videoRef.current.srcObject.getVideoTracks()[0];
track.applyConstraints({
advanced: [{ torch: !flashOn }]
});
setFlashOn(!flashOn);
};
6. 横竖屏适配
javascript
// 使用orientation.js检测屏幕方向
const orientation = new Orientation({
onChange: (event) => {
// 根据event.alpha/beta/gamma判断方向
setIsLandscape(/* 判断逻辑 */);
}
});
// 拍照时根据方向调整
if (isLandscape) {
// 旋转画布90度
ctx.rotate(Math.PI/2);
// 调整绘制位置
}
完整组件结构
jsx
function Camera() {
const videoRef = useRef();
const [zoom, setZoom] = useState(1);
const [photos, setPhotos] = useState([]);
const [flashOn, setFlashOn] = useState(false);
const [isLandscape, setIsLandscape] = useState(false);
// 初始化摄像头
useEffect(() => {
initCamera();
return () => stopCamera();
}, []);
return (
<div className="camera-container">
{/* 视频预览区域 */}
<div className="video-wrapper">
<video ref={videoRef} style={{ transform: `scale(${zoom})` }} />
</div>
{/* 控制区域 */}
<div className="controls">
<button onClick={zoomIn}>放大</button>
<button onClick={zoomOut}>缩小</button>
<button onClick={takePhoto}>拍照</button>
<button onClick={continuousShooting}>连拍</button>
<button onClick={toggleFlash}>
{flashOn ? '关闭手电筒' : '打开手电筒'}
</button>
</div>
{/* 照片预览 */}
<div className="preview">
{photos.map((photo, i) => (
<img key={i} src={photo} alt={`预览${i}`} />
))}
</div>
</div>
);
}
注意事项
- 兼容性处理:不同浏览器API可能有差异
- 性能优化:及时释放摄像头资源
- 移动端适配:处理横竖屏切换
- 权限处理:优雅处理用户拒绝权限的情况
- 图片压缩:大尺寸图片上传前需要压缩
总结
通过getUserMedia
API获取摄像头数据流,结合canvas
实现拍照功能,再添加各种控制功能,就能打造一个功能完善的H5相机组件。这种方案比原生<input type="file">
提供了更好的用户体验和更多自定义功能。