保存和恢复contentEditable div的插入位置

Art*_*rty 13 javascript jquery contenteditable

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

per*_*mon 26

回到2016年:)
在我遇到这个解决方案后,它不适合我,因为我的DOM在每次打字后都完全取代了.我已经做了更多的研究,并提供了简单的解决方案,可以根据角色的位置保存光标,这对我来说非常合适.

想法很简单.

  1. 在插入之前找到charachters的长度并保存它.
  2. 改变DOM.
  3. 使用TreeWalker刚刚走text nodescontext node,直到我们得到正确的计数字符text node和它里面的位置

两个边缘案例:

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

  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){
    var lastNode = null;

    var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT,function next(elem) {
        if(index >= elem.textContent.length){
            index -= elem.textContent.length;
            lastNode = elem;
            return NodeFilter.FILTER_REJECT
        }
        return NodeFilter.FILTER_ACCEPT;
    });
    var c = treeWalker.nextNode();
    return {
        node: c? c: root,
        position: c? index:  0
    };
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

  • 我实际上和@pelican_george 有同样的问题——你的方法效果很好,但它不适用于换行符。一旦您插入换行符,光标就会停留在第一行(即使您已经创建了新行)。用你的例子看看 jsfiddle:https://jsfiddle.net/80ovoxr9 不幸的是,我无法换行工作:( (3认同)

pob*_*oby 11

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

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

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

HTML

<div id=bE contenteditable=true></div>
Run Code Online (Sandbox Code Playgroud)

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}
Run Code Online (Sandbox Code Playgroud)

  • 未捕获的引用错误:bE 未定义 (3认同)
  • 看起来好像是将每个选择范围边界都转换为路径,然后又转换回来。只要在“ innerHTML”更改之前和之后DOM的结构相同(这不能保证是真的),这就是一种很好的方法。 (2认同)

Tim*_*own 8

您可以使用我的跨浏览器范围和选择库Rangy.它有一个选择保存和恢复模块,似乎非常适合您的需求.

这种方法并不复杂:它在每个选定范围的开头和结尾插入标记元素,并使用这些标记元素稍后再次恢复范围边界,这可以在没有Rangy的情况下实现很少的代码(你甚至可以调整Rangy自己的代码).Rangy的主要优点是支持IE <= 8.

  • @thedayturns:这是正确的态度,所以我不怪你:)我很高兴它有所帮助. (3认同)