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

使用Flask-SocketIO实现多端协同编辑时如何保留光标位置

解决contenteditable同步HTML时光标重置问题

直接替换contenteditable元素的innerHTML会重置DOM结构,导致浏览器丢失光标位置。要解决这个问题,需要在更新内容前后手动保存和恢复光标位置,同时避免本地修改与服务器同步的循环触发。

核心实现步骤

1. 编写光标位置保存与恢复函数

这两个函数负责记录当前光标在文本中的偏移量,并在内容更新后重新定位:

// 保存光标相对于文本内容的偏移量
function saveCursorPosition(element) {
    const selection = window.getSelection();
    if (selection.rangeCount === 0) return 0;
    
    const range = selection.getRangeAt(0);
    const preRange = document.createRange();
    preRange.selectNodeContents(element);
    preRange.setEnd(range.startContainer, range.startOffset);
    
    return preRange.toString().length;
}

// 根据偏移量恢复光标位置
function restoreCursorPosition(element, position) {
    const selection = window.getSelection();
    let currentNode = element.firstChild;
    let currentLength = 0;

    // 遍历节点找到对应位置的文本节点
    while (currentNode) {
        if (currentNode.nodeType === Node.TEXT_NODE) {
            if (currentLength + currentNode.length >= position) {
                const range = document.createRange();
                range.setStart(currentNode, position - currentLength);
                range.collapse(true);
                selection.removeAllRanges();
                selection.addRange(range);
                return;
            }
            currentLength += currentNode.length;
        }
        currentNode = currentNode.nextSibling;
    }

    // 如果位置超出文本长度,将光标移到末尾
    const range = document.createRange();
    range.selectNodeContents(element);
    range.collapse(false);
    selection.removeAllRanges();
    selection.addRange(range);
}

2. 修改Socket接收逻辑,加入光标保存与恢复

在接收服务器同步的HTML时,先保存光标位置,更新内容后再恢复:

let isUpdatingFromServer = false; // 标记是否为服务器触发的更新

socket.on("textchanger", (msg) => {
    const element = document.querySelector(".write-text");
    const cursorPos = saveCursorPosition(element);
    
    isUpdatingFromServer = true;
    element.innerHTML = msg;
    restoreCursorPosition(element, cursorPos);
    isUpdatingFromServer = false;
});

3. 过滤服务器触发的Mutation事件

避免服务器同步内容时,本地MutationObserver再次触发上传,造成循环:

$(document).ready(() => {
    var observer = new MutationObserver(function(mutations) {
        if (isUpdatingFromServer) return; // 跳过服务器触发的修改
        
        mutations.forEach(mutation => {
            if (mutation.type == "characterData") {
                socket.emit('changetext', {
                    text: document.querySelector(".write-text").innerHTML, 
                    room: "{{ firstdocument.id }}"
                });
            }
        });
    });
    
    observer.observe(document.querySelector(".write-text"), {
        attributes: true, 
        characterData: true, 
        subtree: true
    });
});

注意事项

  • 上述函数适用于纯文本或简单富文本场景,如果你的文档包含复杂格式(如多级嵌套元素),需要优化光标保存逻辑,直接存储Range对象的节点路径和偏移量。
  • 建议在服务器端实现文本差异算法,只同步修改的部分,而非整个HTML,这样不仅能减少带宽消耗,还能避免频繁替换DOM导致的光标问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 03:35:19