本文介绍如何在 Fabric.js 中直接使用实时摄像头流作为画布背景,避免图像被覆盖或丢失,同时支持添加可交互的 Fabric 对象(如圆形、矩形等),无需将视频帧转为 base64 图片或临时 元素。
本文介绍如何在 fabric.js 中直接使用实时摄像头流作为画布背景,避免图像被覆盖或丢失,同时支持添加可交互的 fabric 对象(如圆形、矩形等),无需将视频帧转为 base64 图片或临时 `` 元素。
Fabric.js 默认会清空原始
✅ 正确方案:自定义 Fabric 对象实现动态背景渲染
最稳定、高效的方式是创建一个继承自 fabric.Object 的自定义类,在其 render() 方法中直接调用 ctx.drawImage(video, ...)。该对象始终位于图层最底层(默认 z-index 最低),且随 Fabric 主循环自动重绘,与视频流同步。
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
以下是精简可靠的实现:
<video id="img-stream" autoplay muted></video><canvas id="img-canvas"></canvas><script src="/uploads/20260726/17850250146a6551f638539.jpg"></script><script>
const canvasEl = document.getElementById('img-canvas');
const video = document.getElementById('img-stream');
const fabricCanvas = new fabric.Canvas('img-canvas', {
backgroundColor: 'transparent',
preserveObjectStack: true,
});
// 自定义视频背景对象
const VideoBackground = fabric.util.createClass(fabric.Object, {
type: 'video-background',
initialize() {
this.callSuper('initialize', {});
// 确保该对象不参与选择、缩放等交互
this.selectable = false;
this.evented = false;
this.hoverCursor = 'default';
},
render(ctx) {
if (!video.readyState || !video.videoWidth) return;
// 将视频完整绘制到 Canvas 上(适配 canvas 尺寸)
const scaleX = fabricCanvas.width / video.videoWidth;
const scaleY = fabricCanvas.height / video.videoHeight;
const scale = Math.min(scaleX, scaleY);
const w = video.videoWidth * scale;
const h = video.videoHeight * scale;
const left = (fabricCanvas.width - w) / 2;
const top = (fabricCanvas.height - h) / 2;
ctx.save();
ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight,
left, top, w, h);
ctx.restore();
}
});
// 添加背景对象(必须置于最底层)
fabricCanvas.add(new VideoBackground());
fabricCanvas.sendToBack(fabricCanvas.item(0)); // 确保在底部
// 添加可编辑图形(例如红色圆圈)
const circle = new fabric.Circle({
left: fabricCanvas.width / 2,
top: fabricCanvas.height / 2,
radius: 40,
fill: 'rgba(255,0,0,0.3)',
stroke: 'red',
strokeWidth: 3,
selectable: true,
evented: true
});
fabricCanvas.add(circle);
// 启动摄像头
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
video.srcObject = stream;
video.play();
fabricCanvas.renderAll(); // 初始渲染
})
.catch(err => console.error('摄像头访问失败:', err));
// 持续重绘以同步视频帧(推荐使用 requestAnimationFrame)
function animate() {
fabricCanvas.renderAll();
requestAnimationFrame(animate);
}
animate();
</script>
⚠️ 注意事项与最佳实践
-
不要使用 setBackgroundImage():该方法仅接受 URL 或
元素,不支持实时
- 避免修改私有方法(如 _renderBackground):虽然可行,但违反 Fabric.js API 稳定性约定,未来版本升级可能导致兼容性问题。
- 尺寸适配建议:示例中采用「居中缩放」策略(保持宽高比),你可根据需求改为拉伸填充(ctx.drawImage(video, 0, 0, fabricCanvas.width, fabricCanvas.height))。
- 性能优化:若仅需静态快照,可在捕获后用 fabric.Image.fromURL(dataUrl, ...) 加载;但对实时流,自定义 render() 是唯一可靠方式。
- 移动端兼容性:确保
通过此方案,你既能保留 Fabric.js 强大的对象管理能力(拖拽、缩放、序列化、导出),又能无缝集成实时视频流作为动态背景,真正实现“所见即所得”的图像标注体验。










