【问题标题】:Saving and Restoring caret position for contentEditable div保存和恢复 contentEditable div 的插入符号位置
【发布时间】:2011-06-02 09:05:47
【问题描述】:

我有一个contentEditable div,其中的innerHTML 可以在编辑时通过AJAX 更新。问题是,当您更改 div 的内容时,它会将光标移动到 div 的末尾(或根据浏览器失去焦点)。在更改 innerHTML 之前存储插入符号位置然后恢复它的良好跨浏览器解决方案是什么?

【问题讨论】:

    标签: javascript jquery contenteditable


    【解决方案1】:

    回到 2016 年 :)
    在我在这里遇到解决方案并且它们不适合我之后,因为每次键入后我的 DOM 都被完全替换了。我做了更多的研究,并提出了一个简单的解决方案,可以按字符位置保存光标,这对我来说完美

    这个想法很简单。

    1. 找出插入符号前的字符长度并保存。
    2. 更改 DOM。
    3. 使用TreeWalkercontext node 中的text nodes 上行走并计数字符,直到找到正确的text node 和其中的位置

    两个边缘情况:

    1. 内容完全删除,因此没有text node
      所以:将光标移动到上下文节点的开头

    2. 内容少于指向的index
      所以:将光标移动到最后一个节点的末尾

    function saveCaretPosition(context){
        var selection = window.getSelection();
        var range = selection.getRangeAt(0);
        range.setStart(  context, 0 );
        var len = range.toString().length;
    
        return function restore(){
            var pos = getTextNodeAtPosition(context, len);
            selection.removeAllRanges();
            var range = new Range();
            range.setStart(pos.node ,pos.position);
            selection.addRange(range);
    
        }
    }
    
    function getTextNodeAtPosition(root, index){
        const NODE_TYPE = NodeFilter.SHOW_TEXT;
        var treeWalker = document.createTreeWalker(root, NODE_TYPE, function next(elem) {
            if(index > elem.textContent.length){
                index -= elem.textContent.length;
                return NodeFilter.FILTER_REJECT
            }
            return NodeFilter.FILTER_ACCEPT;
        });
        var c = treeWalker.nextNode();
        return {
            node: c? c: root,
            position: index
        };
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.5.1/prism.min.js"></script>
    <link href="https://rawgit.com/PrismJS/prism/gh-pages/themes/prism.css" rel="stylesheet"/>
    <style>
      *{
        outline: none
        }
    </style>  
    <h3>Edit the CSS Snippet </H3>
    <pre>
        <code class="language-css" contenteditable=true >p { color: red }</code>
    </pre>
    
    <script >
      var code = document.getElementsByTagName('code')[0];
      
      code.addEventListener('input',function () {
            var restore = saveCaretPosition(this);
            Prism.highlightElement(this);
            restore();
        })
    </script>

    【讨论】:

    • 如果内容可编辑元素中有换行符和其他格式元素怎么办?
    • 你的意思是像
      ?它仍然应该工作。该代码为富文本编辑器构建,可在用户编写时更改光标周围的元素
    • 我有一个可编辑的内容,带有中断等,它可以工作。如果您将它用于撤消功能,请将最后一次按键存储在 onKeyDown 处理程序中并使用range.setStart(pos.node ,pos.position-(lastKeypress == 13 ? 0:1)); 来防止光标移动:-)
    • 我实际上和@pelican_george 有同样的问题 - 你的方法效果很好,但它不适用于换行符。一旦您插入换行符,光标就会停留在第一行(即使您已经创建了新行)。看看你的例子中的 jsfiddle:jsfiddle.net/80ovoxr9 不幸的是,我无法换行 :(
    • 似乎改变 以显示内联块帮助与换行符。
    【解决方案2】:

    我知道这是一个古老的线程,但我想我会提供一个替代的非库解决方案

    http://jsfiddle.net/6jbwet9q/9/

    在 chrome、FF 和 IE10+ 中测试允许您更改、删除和恢复 html,同时保留插入符号的位置/选择。

    HTML

    <div id=bE contenteditable=true></div>
    

    JS

    function saveRangePosition()
      {
      var range=window.getSelection().getRangeAt(0);
      var sC=range.startContainer,eC=range.endContainer;
    
      A=[];while(sC!==bE){A.push(getNodeIndex(sC));sC=sC.parentNode}
      B=[];while(eC!==bE){B.push(getNodeIndex(eC));eC=eC.parentNode}
    
      return {"sC":A,"sO":range.startOffset,"eC":B,"eO":range.endOffset};
      }
    
    function restoreRangePosition(rp)
      {
      bE.focus();
      var sel=window.getSelection(),range=sel.getRangeAt(0);
      var x,C,sC=bE,eC=bE;
    
      C=rp.sC;x=C.length;while(x--)sC=sC.childNodes[C[x]];
      C=rp.eC;x=C.length;while(x--)eC=eC.childNodes[C[x]];
    
      range.setStart(sC,rp.sO);
      range.setEnd(eC,rp.eO);
      sel.removeAllRanges();
      sel.addRange(range)
      }
    
    function getNodeIndex(n){var i=0;while(n=n.previousSibling)i++;return i}
    

    【讨论】:

    • 这看起来好像将每个选择范围边界转换为路径并再次返回。这是一个很好的方法,只要 DOM 的结构在 innerHTML 更改之前和之后是相同的,这不能保证是真的。
    • 是否可以为多个 contenteditable div 修复此代码?这样我就可以选择 1 of 3 div contenteditable,然后检索我要插入的位置。
    • Uncaught ReferenceError: bE is not defined
    【解决方案3】:

    更新:我已将 Rangy 的代码移植到独立的 Gist:

    https://gist.github.com/timdown/244ae2ea7302e26ba932a43cb0ca3908

    原答案

    您可以使用Rangy,我的跨浏览器范围和选择库。它有一个selection save and restore module,似乎非常适合您的需求。

    该方法并不复杂:它在每个选定范围的开头和结尾插入标记元素,并使用这些标记元素稍后再次恢复范围边界,这可以在没有 Rangy 的情况下用不多的代码实现(你甚至可以适应Rangy's own code)。 Rangy 的主要优点是支持 IE

    【讨论】:

    • 太棒了。我对使用 SO 上某个人的随机库感到有些不安,但它在 2 行代码中完成了我想要的。谢谢!
    • @thedayturns:这是正确的态度,所以我不怪你 :) 很高兴它有帮助。
    • @TimDown Rangy 是否支持多个 contenteditable div?就像,将插入符号的位置保存在三个不同的 div 上。原因是,我想为 3 个不同的字段使用 1 个编辑器。
    • 如果我完全替换整个 div 的内容,我不确定该方法如何工作
    • @Norman:我将 Rangy 的代码移植到了一个独立的 Gist:gist.github.com/timdown/244ae2ea7302e26ba932a43cb0ca3908。显然,如果你愿意,你可以把选择的东西删掉,但我把它留了下来,以防它对其他人有用。
    猜你喜欢
    • 1970-01-01
    • 2021-08-28
    • 1970-01-01
    • 1970-01-01
    • 2012-05-11
    • 2019-09-16
    • 1970-01-01
    • 2014-07-29
    • 2011-06-17
    相关资源
    最近更新 更多