
本文介绍如何将未保存的 Webcam 实时视频流作为 Fabric.js 画布的动态背景,避免调用 canvas.add() 后原图消失的问题,并支持在视频背景上自由添加、拖拽、编辑 Fabric 对象。
本文介绍如何将未保存的 webcam 实时视频流作为 fabric.js 画布的动态背景,避免调用 `canvas.add()` 后原图消失的问题,并支持在视频背景上自由添加、拖拽、编辑 fabric 对象。
Fabric.js 默认会完全接管 <canvas> 的渲染流程,因此直接使用原生 drawImage() 绘制的视频帧会在 Fabric 调用 renderAll() 时被清空——这是导致“图像消失”的根本原因。解决思路不是将视频转为静态图片再设为背景(如 setBackgroundImage),而是让 Fabric 在每次渲染周期中主动绘制视频帧,使其成为画布底层内容,从而与上层 Fabric 对象共存。
✅ 推荐方案:自定义 Fabric 对象实现视频背景渲染
最稳定、可维护的方式是创建一个继承自 fabric.Object 的自定义类,在其 render() 方法中调用 ctx.drawImage() 渲染视频流:
const video = document.getElementById('img-stream');
const canvas = new fabric.Canvas('img-canvas', {
backgroundColor: 'transparent', // 避免遮盖视频
width: window.innerWidth,
height: window.innerHeight,
});
// 自定义视频背景对象
const VideoBackground = fabric.util.createClass(fabric.Object, {
type: 'videoBackground',
initialize() {
this.callSuper('initialize', {});
},
render(ctx) {
ctx.save();
// 关键:适配 Fabric 视口变换(如缩放/平移),确保视频始终铺满画布
const { viewportTransform } = canvas;
const offsetX = -viewportTransform[4];
const offsetY = -viewportTransform[5];
const width = video.videoWidth || canvas.width;
const height = video.videoHeight || canvas.height;
ctx.drawImage(video, offsetX, offsetY, width, height);
ctx.restore();
},
});
// 添加到画布(必须置于所有其他对象之下)
const bg = new VideoBackground();
canvas.add(bg);
canvas.sendToBack(bg); // 确保背景在最底层⚠️ 注意:务必调用 canvas.sendToBack(bg),否则新添加的对象可能被背景覆盖。
✅ 添加可交互 Fabric 对象(示例:红色矩形)
const rect = new fabric.Rect({
left: 100,
top: 100,
width: 120,
height: 80,
fill: 'rgba(255, 0, 0, 0.6)',
stroke: '#ff3333',
strokeWidth: 2,
selectable: true,
evented: true,
hasControls: true,
});
canvas.add(rect);? 持续渲染:启用 requestAnimationFrame 循环
Fabric 不会自动重绘视频帧(视频是动态流),因此需手动触发持续渲染:
将一个或多个视频上传至YouTube。当用户想“上传到YouTube”“发YouTube”“批量上传”“upload to YouTube”“post videos to YouTube”或发布到YouTube时使用此技能。
let animationId;
function renderLoop() {
canvas.renderAll(); // 触发所有对象(含 VideoBackground)重绘
animationId = requestAnimationFrame(renderLoop);
}
// 在视频开始播放后启动循环
video.addEventListener('play', () => {
if (!animationId) renderLoop();
});
// 暂停时清理
video.addEventListener('pause', () => {
cancelAnimationFrame(animationId);
animationId = null;
});? 不推荐方案:篡改 _renderBackground
虽然可通过覆盖私有方法实现类似效果:
canvas._renderBackground = function(ctx) {
if (video.readyState === video.HAVE_ENOUGH_DATA) {
ctx.drawImage(video, 0, 0, this.width, this.height);
}
};但该方式依赖 Fabric 内部实现,极易因版本升级失效,且无法响应视口变换(如缩放、平移),不适用于生产环境。
✅ 补充:初始化摄像头流(完整逻辑)
if (navigator.mediaDevices?.getUserMedia) {
navigator.mediaDevices.getUserMedia({ video: true, audio: false })
.then(stream => {
video.srcObject = stream;
video.play();
})
.catch(err => console.error('摄像头访问被拒绝:', err));
} else {
console.warn('当前浏览器不支持 MediaDevices API');
}✅ 总结
- ❌ 不要尝试将 toDataURL() 结果设为背景图——它生成的是静态快照,且 setBackgroundImage 无法处理实时流;
- ✅ 使用自定义 fabric.Object 在 render() 中绘制视频,是 Fabric 官方推荐的扩展方式;
- ✅ 始终调用 sendToBack() 确保背景层级最低;
- ✅ 必须配合 requestAnimationFrame 实现视频帧连续更新;
- ✅ 所有 Fabric 对象(Circle、Rect、Text 等)均可正常添加、选择、变换,与视频背景完全兼容。
此方案兼顾性能、稳定性与可扩展性,适用于在线标注、AR 教学、实时涂鸦等需要视频底图 + 图形编辑的场景。

















