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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4576694/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-25 14:10:30  来源:igfitidea点击:

Saving and Restoring caret position for contentEditable div

javascriptjquerycontenteditable

提问by Arty

I have a contentEditablediv, the innerHTMLof which can be updated through AJAX while editing. The problem is that when you change the contents of the div it moves the cursor to the end of the div (or loses focus depending on the browser). What is a good cross-browser solution to store caret position before changing innerHTMLand then to restore it?

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

回答by pery mimon

back to 2016 :)
After I came across this solution, and it did not suit me, because my DOM replaced completely after each typing . I've done more research and come with simple solution that save the cursor by character's position that work perfectfor me.

回到 2016 年 :)
在我遇到这个解决方案后,它不适合我,因为我的 DOM 在每次输入 . 我做更多的研究,并通过人物的位置都设有简单的解决方案,节省光标的工作完美的我。

Idea is very simple .

想法很简单。

  1. find the length of charachters before caret and save it .
  2. change the DOM.
  3. using TreeWalkerto walk just on text nodesof context nodeand counting characters until we got the right text nodeand the position inside it
  1. 在插入符号之前找到字符的长度并保存它。
  2. 改变DOM。
  3. 使用TreeWalker走刚上text nodescontext node和计数的字符,直到我们得到了正确的text node和里面的位置

Two edge case:

两种边缘情况:

  1. content removed completely so there is no text node:
    so: move cursor to start of context node

  2. there is less content then the indexpointed on :
    so: move cursor to the end of the last 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){
    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: 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>

回答by poby

I know this is an ancient thread but I thought I would provide an alternative non-library solution

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

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

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

Tested in chrome, FF, and IE10+ Allows you to change, delete and restore html while retaining caret position/selection.

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

HTML

HTML

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

JS

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}

回答by Tim Down

Update: I've ported Rangy's code to a standalone Gist:

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

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

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

Original answer

原答案

You could use Rangy, my cross-browser range and selection library. It has a selection save and restore modulethat seems well-suited to your needs.

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

The approach is not complicated: it inserts marker elements at the beginning and end of each selected range and uses those marker elements to restore the range boundaries again later, which could be implemented without Rangy in not much code (and you could even adapt Rangy's own code). The main advantage of Rangy is support for IE <= 8.

该方法并不复杂:它在每个选定范围的开始和结束处插入标记元素,然后使用这些标记元素再次恢复范围边界,这可以在没有 Rangy 的情况下实现,代码不多(您甚至可以改编Rangy 自己的代码)。Rangy 的主要优点是支持 IE <= 8。