核心是用 WebSocket 实现低延迟全双工通信,传输语义化操作指令(如 stroke/circle/clear)与坐标数据,统一 JSON 格式(含 type/id/points/attrs),客户端批量解析绘制并支持新用户历史同步与断线重连。

核心是用 WebSocket 建立低延迟、全双工的连接,把每个用户的绘图动作实时广播给所有人,并在各自浏览器中精准还原——不是传图片,而是传“操作指令”和“坐标数据”。
统一数据结构,避免渲染不一致
不要直接同步 Canvas 的像素或 SVG 的原始字符串。应定义轻量、语义明确的 JSON 消息格式,例如:
- type:标识操作类型,如 "stroke"(画笔)、"circle"(圆形)、"clear"(清空)
- id:用户唯一标识(如 "user_789"),用于区分颜色与轨迹归属
- points:数组形式的坐标序列,如 [[120,80],[125,83],[132,87]],适用于连续笔迹
- attrs:可选属性,如 {color: "#e74c3c", lineWidth: 3, tool: "pen"}
所有客户端共用同一套解析逻辑,确保相同数据在不同设备上渲染效果一致。
精准捕获与高效广播
监听鼠标/触控事件时,不等松手才发消息,而采用“节流+增量”策略:
立即学习“前端免费学习笔记(深入)”;
- mousemove 或 touchmove 触发时,每 30–50ms 收集一次新点,凑够 3–5 个再打包发送,减少频次又不失流畅
- 起笔(mousedown/touchstart)发 start 消息;抬笔(mouseup/touchend)发 end 消息,便于客户端做路径闭合或优化
- 清空画布、切换颜色、撤销等控制操作,单独用文本帧发送,结构清晰易调试
本地还原要兼顾性能与体验
收到消息后,前端不逐点重绘,而是批量处理:
- 将同 id、同 color 的连续 points 合并为一条 Canvas path,用
beginPath()→moveTo()→lineTo()一次性绘制 - 避免频繁调用
ctx.stroke(),改用离屏 canvas 预渲染再贴图,尤其适合高密度笔迹 - 对新加入用户,服务端可补发最近 10 秒的操作历史(带时间戳),实现“加入即同步”,无需加载整张图
基础稳定性保障不可少
真实协作中网络波动、重复连接、意外断开很常见,需简单但有效的应对:
- 每个消息附带单调递增的 sequence ID 或毫秒级 timestamp,客户端按序消费,丢弃旧包
- 用户断开时,服务端标记其状态,广播 "user_left" 事件;重连后分配原 id,延续颜色与权限
- Canvas 本身不支持矢量缩放,若需无限画布,建议用
transform平移+缩放容器 div,保持 canvas 尺寸合理(如 2000×2000),避免内存溢出



















