不能用 innerhtml 替换或轮询 diff——必须用结构化原子操作(如 insert/delete)替代快照,带 docid、clientid、version、timestamp,并分层重绘 dom、光标与选区,兼顾离线容错。

直接用 innerHTML 替换或轮询 diff 是行不通的——重绘不是“重新渲染整页”,而是精准响应他人操作、保留本地状态、避免 DOM 闪跳和光标丢失。关键在「操作粒度」和「渲染时机」。
为什么不能监听 input 或 contenteditable 的 change 事件发整段 HTML
常见错误是把 document.body.innerHTML 当作变更内容发给服务端。这会立刻导致:
- 两个用户同时在
<div> 里插入文本,后到的快照直接覆盖前者,无合并逻辑 <li> <code>innerHTML不含光标位置、选区、折叠状态等 UI 上下文,客户端无法还原焦点 - 10KB 的 HTML 每次按键都全量传输,5 个用户并发时带宽和解析压力陡增
- 网络抖动导致消息乱序,“第 3 版”比“第 2 版”晚到,别人看到 HTML 突然“倒退”
-
type必须是语义化动作:"insert"、"delete"、"replace"、"format" -
index是逻辑偏移(字符数 or AST 节点路径),不是 DOM 坐标;需配合当前文档版本校准 - 每条操作必须含
docId、clientId、version(本地已知最新版)、timestamp(服务端打戳优先) - 用协作引擎(如
Y.Doc或ShareDB.Doc)驱动 DOM 变更,只更新受影响节点,保留原有引用 - 光标和选区单独同步:服务端广播
{type:"selection", clientId:"u3", anchor: {nodeId:"p_123", offset:5}, focus: {...}},前端用Range和SelectionAPI 定位并包裹零宽字符 - 为每个用户渲染独立光标层(绝对定位
<span class="cursor" style="left:120px;top:85px"></span>),避免干扰主编辑流 - 切后台前缓存当前
Y.Doc快照 + 最近 20 条操作日志,恢复时先 apply 快照再重放日志 - 移动端 Safari 对
getSelection()支持不稳定,fallback 到document.caretRangeFromPoint(x, y)(仅限点击触发) - 重连后不全量重绘,而是比对本地
version与服务端快照,只 patch 差异节点,避免闪屏
必须用结构化操作替代快照,且带完整上下文
前端应监听编辑器底层变更(如 Tiptap 的 onUpdate、CodeMirror 的 dispatch),生成原子操作对象:
示例:{"type":"insert","docId":"html-abc","clientId":"u7","index":42,"text":"<p>Hello</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill5806" title="html-deploy"><img
src="https://img.php.cn/upload/skill/000/000/081/179066538882434.jpg" alt="html-deploy" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill5806" title="html-deploy" class="overflowclass">html-deploy</a>
<p class="overflowclass">使用 htmlcode.fun 将 HTML 内容或文件部署到网页,适用于用户要求“部署到网页”“托管此 HTML”“生成此前端...的实时链接”等场景。</p>
</div>
<a rel="nofollow" href="/xiazai/skill5806" title="html-deploy" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>"}
重绘必须分层:DOM 更新 + 光标/选区同步 + 视觉反馈
收到他人操作后,不能直接 innerHTML = newText,否则滚动归零、光标消失、样式重置:
移动端与离线场景下的重绘容错
真实协作中,页面可能被切后台、WebView 暂停、Safari 强制回收内存:
重绘的复杂点不在“怎么画”,而在“什么时候画、画哪些、画完后光标在哪”。任何跳过操作语义、直奔 DOM 替换的做法,都会在三人以上协作时暴露一致性裂缝。










