websocket负责实时同步画笔事件,canvas负责本地重放绘制,通过传输start/move/end/clear等轻量事件实现低延迟协同涂鸦。

WebSocket 负责实时同步画笔动作,Canvas 负责本地绘制和渲染,两者配合就能实现低延迟、多用户协同的涂鸦画板。核心不是“把所有像素传过去”,而是只传“画笔事件”(如按下、移动、抬笔、颜色、粗细),服务端广播给其他客户端,各客户端用 Canvas 重放这些操作。
1. WebSocket 建立连接并约定消息协议
客户端通过 new WebSocket('ws://your-server:port') 连接服务端。关键是要定义轻量、可扩展的消息格式,比如:
- start:{ type: 'start', x, y, color, lineWidth } —— 按下画笔
- move:{ type: 'move', x, y } —— 移动画笔(高频)
- end:{ type: 'end' } —— 抬起画笔,结束本次绘制
- clear:{ type: 'clear' } —— 清空画布(需校验权限)
服务端收到后不做处理,直接用 ws.send(JSON.stringify(msg)) 广播给除自己外的所有在线连接(或全部,由业务决定)。避免传图片、Base64 或完整 canvas 数据——太重、太慢。
2. Canvas 绘制逻辑要支持“重放”而非“截图”
每个客户端的 Canvas 只负责响应收到的 WebSocket 消息,自己画自己的线段,不依赖别人画面的快照。示例绘制函数:
let isDrawing = false;
let lastX = 0, lastY = 0;
function handleDrawEvent(event) {
const ctx = canvas.getContext('2d');
switch (event.type) {
case 'start':
isDrawing = true;
lastX = event.x; lastY = event.y;
ctx.strokeStyle = event.color || '#000';
ctx.lineWidth = event.lineWidth || 2;
ctx.lineCap = 'round';
break;
case 'move':
if (!isDrawing) return;
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(event.x, event.y);
ctx.stroke();
lastX = event.x; lastY = event.y;
break;
case 'end':
isDrawing = false;
break;
}
}
注意:不要用 ctx.clearRect() 清整个画布再重绘——那是回放历史全量路径,适合初始化加载;实时协作中应只追加新笔画,保证响应速度。
3. 处理并发与状态一致性
多人同时画时,消息可能乱序或延迟。简单但有效的做法:
- 服务端不做顺序保证,客户端按接收顺序执行(对涂鸦影响小)
- 为每条消息加时间戳或序列号,客户端可丢弃明显滞后的 move(比如比本地最新事件晚 500ms)
- 用户标识(如
userId)随事件一起发送,方便在 Canvas 上用不同颜色/昵称标注笔迹来源 - 首次连接时,服务端主动推送当前画布“快照”(即最近 N 条历史 draw 事件数组),让新用户快速同步
4. 实际部署要注意的细节
开发阶段可用 ws(Node.js)或 Socket.IO(兼容降级),但生产环境建议:
- 用 Nginx 做 WebSocket 代理,配置
proxy_http_version 1.1和Upgrade头 - Canvas 设置固定宽高(
canvas.width = 800; canvas.height = 600;),避免 CSS 缩放导致坐标错位 - 移动端需监听
touchstart/touchmove/touchend,换算 clientX/Y 到 canvas 坐标(考虑 offset 和 scale) - 添加简单心跳(ping/pong)保活,防止 NAT 超时断连
不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











