Socket.io事件已广播但Fabric.js协同绘图无实时同步问题
协同绘图问题:Fabric.js画布实时同步失败
问题描述
我基于fabric.js实现了一个画布,支持用户A和用户B登录后协同操作,目标是实现屏幕实时更新。后端日志显示事件已成功广播,但双方的绘图内容无法互相显示。
后端代码
io.on('connection', (socket) => { console.log(`User ${socket.user.username} with ID ${socket.user.id} is connected`); socket.on('draw:start', ({ x, y }) => { socket.broadcast.emit('draw:start', {x, y}); console.log("draw:start event broadcasted") }); socket.on('draw:progress', ({ x, y }) => { socket.broadcast.emit('draw:progress', { x, y }); console.log("draw:progress event broadcasted") }); socket.on('draw:end', () => { socket.broadcast.emit('draw:end'); console.log("draw:end event broadcasted") }); socket.on('disconnect', () => { console.log(`User ${socket.user.username} with ID ${socket.user.id} disconnected`); }); });
前端代码
useEffect(() => { const canvas = canvasRef.current; const fabricCanvas = new fabric.Canvas(canvas); fabricCanvasRef.current = fabricCanvas; fabricCanvas.isDrawingMode =true; fabricCanvas.freeDrawingBrush.width = 5; fabricCanvas.freeDrawingBrush.color = 'black'; const token = localStorage.getItem('token'); console.log("Token from frontend side ", token); socketRef.current = io('http://localhost:8080', { query: { token: token } }); fabricCanvas.on('mouse:down', (event) => { const { x, y } = event.pointer; console.log(`Mouse down - X: ${x}, Y: ${y}`); socketRef.current.emit('draw:start', { x, y }); }); fabricCanvas.on('mouse:move', (event) => { if (!fabricCanvasRef.current.isDrawingMode) return; const { x, y } = event.pointer; console.log(`Mouse move - X: ${x}, Y: ${y}`); socketRef.current.emit('draw:progress', { x, y }); }); fabricCanvas.on('mouse:up', () => socketRef.current.emit('draw:end')); return () => { fabricCanvas.off('mouse:down', (event) => { const { x, y } = event.pointer; console.log(`Mouse down - X: ${x}, Y: ${y}`); socketRef.current.emit('draw:start', { x, y }); }); fabricCanvas.off('mouse:move', (event) => { if (!fabricCanvasRef.current.isDrawingMode) return; const { x, y } = event.pointer; console.log(`Mouse move - X: ${x}, Y: ${y}`); socketRef.current.emit('draw:progress', { x, y }); }); fabricCanvas.off('mouse:up', () => socketRef.current.emit('draw:end')); socketRef.current.disconnect(); } }, []);
问题原因及解决方案
核心问题是前端只实现了事件发送逻辑,没有监听后端广播的事件,同时直接同步鼠标坐标无法在对方画布上生成有效图形,需要模拟Fabric.js的绘图流程。
修改后的前端代码(添加事件监听与远程绘图处理)
useEffect(() => { const canvas = canvasRef.current; const fabricCanvas = new fabric.Canvas(canvas); fabricCanvasRef.current = fabricCanvas; fabricCanvas.isDrawingMode = true; fabricCanvas.freeDrawingBrush.width = 5; fabricCanvas.freeDrawingBrush.color = 'black'; // 用于远程绘图的临时画笔 let remoteBrush = null; const token = localStorage.getItem('token'); console.log("Token from frontend side ", token); socketRef.current = io('http://localhost:8080', { query: { token: token } }); // 本地绘图事件发送 fabricCanvas.on('mouse:down', (event) => { const { x, y } = event.pointer; socketRef.current.emit('draw:start', { x, y }); }); fabricCanvas.on('mouse:move', (event) => { if (!fabricCanvas.isDrawingMode) return; const { x, y } = event.pointer; socketRef.current.emit('draw:progress', { x, y }); }); fabricCanvas.on('mouse:up', () => { socketRef.current.emit('draw:end'); }); // 监听后端广播的远程绘图事件 socketRef.current.on('draw:start', ({ x, y }) => { // 创建远程画笔,同步本地画笔配置 remoteBrush = new fabric.PencilBrush(fabricCanvas); remoteBrush.width = fabricCanvas.freeDrawingBrush.width; remoteBrush.color = fabricCanvas.freeDrawingBrush.color; // 触发远程画笔的起始动作 remoteBrush.onMouseDown({ pointer: { x, y } }); }); socketRef.current.on('draw:progress', ({ x, y }) => { if (remoteBrush) { remoteBrush.onMouseMove({ pointer: { x, y } }); } }); socketRef.current.on('draw:end', () => { if (remoteBrush) { remoteBrush.onMouseUp(); remoteBrush = null; } }); return () => { // 移除本地事件监听 fabricCanvas.off('mouse:down'); fabricCanvas.off('mouse:move'); fabricCanvas.off('mouse:up'); // 移除socket事件监听 socketRef.current.off('draw:start'); socketRef.current.off('draw:progress'); socketRef.current.off('draw:end'); socketRef.current.disconnect(); } }, []);
关键修改点
- 添加socket事件监听:新增对后端广播的
draw:start、draw:progress、draw:end事件的监听逻辑 - 模拟远程绘图流程:使用Fabric.js原生的
PencilBrush类,复刻远程用户的绘图动作,确保画笔样式与本地一致 - 优化事件移除逻辑:直接调用
off方法移除所有对应事件监听,解决原代码中匿名函数无法正确解绑的问题
内容的提问来源于stack exchange,提问作者chan_d12
相关产品推荐
相关产品推荐

