Aller au contenu principal
NUKOE

Construire un tableau blanc collaboratif temps réel avec Svelte et WebSockets

• 8 min •
Illustration d'un tableau blanc collaboratif connecté via WebSockets avec Svelte.

使用 Svelte 和 WebSockets 构建实时协作白板

当谈到现代协作工具时,Miro 或 FigJam 等名字会立刻浮现在脑海中。在其流畅的表象背后,隐藏着严峻的技术挑战:状态同步、冲突管理和近乎零的延迟。但我们是否必须用奇特的协议或实时数据库来重新发明轮子?不一定。借助 Svelte 和熟练掌握的 WebSockets,可以在不牺牲简洁性的情况下构建一个高性能的协作白板。

本文基于类似项目的经验和社区反馈,逐步指导您构建这样一个应用程序。我们将讨论架构、同步、用户管理以及那些带来差异的优化。

基础:Svelte 和 WebSockets,一对获胜组合

Svelte 以其原生响应性脱颖而出:响应式变量无需额外开销即可更新 DOM。结合 WebSockets(支持持久双向通信),我们获得了实时应用的理想基础。

在我们的案例中,每个绘图操作(绘制、擦除、移动元素)都通过 WebSocket 发送到 Node.js 服务器,然后服务器将其广播给其他客户端。全局状态在服务器端维护,但每个客户端都拥有本地副本以确保即时响应。

架构:服务器作为指挥

服务器扮演核心角色:它接收事件、验证事件并重新分发事件。为避免冲突,每个事件都带有时间戳和唯一 ID。当同一形状发生冲突时,以最后收到的事件为准——这是一种简单但有效的“最后写入获胜”方法,适用于白板。

会话管理至关重要:每个白板都有一个唯一标识符。客户端连接到特定房间,服务器仅将事件传输给该房间的成员。这限制了负载并保证了隐私。

状态管理:本地与远程之间的平衡

一个常见的陷阱是实时同步所有内容,包括悬停形状等瞬态状态。最好区分:

  • 永久事件:绘制、移动、属性修改——发送到服务器并持久化。
  • 瞬时事件:其他用户的光标、临时选择——通过 WebSocket 本地广播但不持久化。

这种区分减少了服务器负载,并避免了数据库被无用信息填满。

用户体验:他人的光标,协作的窗口

实时看到协作者的光标会营造一种存在感。位置数据以高频率(每 50 毫秒)发送,但不保证送达。为避免饱和,客户端使用节流。

注意:白板的滚动或缩放必须考虑在内。每个鼠标事件都转换为逻辑坐标(相对于画布),而不是屏幕像素,以便所有用户都能在正确位置看到光标。

冲突管理:多人模式下的撤销/重做

在协作环境中,撤销/重做是一个难题。采用的解决方案:每个用户拥有自己的本地撤销栈,但撤销操作被转换为“反向”事件并发送到服务器。因此,撤销一条线相当于向所有人发送一个“删除此线”事件。这种方法虽然更复杂,但可以避免不一致。

为了深入探讨,Liveblocks 提供了对多人撤销/重做策略的出色分析,这启发了我们的实现。

性能和可扩展性

对于中等使用量(同一白板上少于 100 个并发用户),单个 Node.js 服务器就足够了。超过这个数量,需要考虑分布式架构,使用 Redis 在实例之间共享状态。类似项目中提到的 NATS 也可以作为消息传递骨干。

在客户端,HTML5 Canvas 优于 SVG,因为渲染性能更好。形状每帧重新绘制,但只有修改过的区域通过脏矩形更新。

部署和实际考虑

WebSocket 服务器可以部署在 Heroku 或 Fly.io 等平台上。注意长连接:负载均衡器的超时时间必须配置为不切断 WebSocket。生产环境必须使用 SSL。

在存储方面,像 SQLite 或 PostgreSQL 这样的简单数据库可以记录白板的最终状态(形状列表及其属性)。对于更高级的实时需求,Supabase Realtime 提供了一站式解决方案。

经验教训

在构建原型时,发现了几个陷阱:

  • 不要忽视压缩:WebSocket 消息可以使用 permessage-deflate 压缩以减少带宽。
  • 处理重连:连接丢失后,客户端必须请求完整的白板状态以重新同步。
  • 用真实用户测试:单元测试无法替代数十人同时绘制的真实会话。

结论

使用 Svelte 和 WebSockets 构建协作白板是一个既容易上手又富有教育意义的项目。通过遵循简单但健壮的架构,正确处理状态和冲突,您可以获得一个流畅且令人愉悦的应用程序。Svelte 等现代工具简化了响应性,而 WebSockets 确保了实时通信。

那么,准备好开始了吗?打开您的编辑器,安装一些依赖项,从简单的鼠标坐标交换开始。剩下的将通过迭代完成。

进一步阅读