You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
    }
}, []);

关键修改点

  1. 添加socket事件监听:新增对后端广播的draw:start、draw:progress、draw:end事件的监听逻辑
  2. 模拟远程绘图流程:使用Fabric.js原生的PencilBrush类,复刻远程用户的绘图动作,确保画笔样式与本地一致
  3. 优化事件移除逻辑:直接调用off方法移除所有对应事件监听,解决原代码中匿名函数无法正确解绑的问题

内容的提问来源于stack exchange,提问作者chan_d12

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 20:13:12