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

IT技术 javascript jquery contenteditable
2021-02-28 02:09:16

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

3个回答

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

这个想法很简单。

  1. 找到插入符号前的字符长度并保存。
  2. 改变DOM。
  3. 使用TreeWalker走刚上text nodescontext node和计数的字符,直到我们得到了正确的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>

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

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

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}
未捕获的 ReferenceError:未定义 bE
2021-04-29 02:09:16
是否可以为多个 contenteditable div 修复此代码?这样我就可以选择 3 个 div contenteditable 中的 1 个,然后检索我想要插入的位置。
2021-05-02 02:09:16
这看起来好像将每个选择范围边界转换为路径并再次返回。这是一个很好的方法,只要 DOM 的结构在innerHTML更改前后是相同的,这不能保证是真的。
2021-05-04 02:09:16

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

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

原答案

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

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

极好的。我对在 SO 上使用某个人的随机库感到有些不安,但它在 2 行代码中完成了我想要的。谢谢!
2021-05-01 02:09:16
如果我完全替换整个 div 的内容,我不确定这种方法如何工作
2021-05-01 02:09:16
@Norman:我将 Rangy 的代码移植到了一个独立的 Gist:gist.github.com/timdown/244ae2ea7302e26ba932a43cb0ca3908显然,如果您愿意,您可以删除选择内容,但我保留了它以防对其他人有用。
2021-05-14 02:09:16
@thedayturns:这是正确的态度,所以我不怪你:) 我很高兴它有所帮助。
2021-05-15 02:09:16
@TimDown Rangy 是否支持多个 contenteditable div?就像,在三个不同的 div 上保存插入符号的位置。原因是,我想对 3 个不同的领域使用 1 个编辑器。
2021-05-18 02:09:16