简体   繁体   English

为 contentEditable div 保存和恢复插入符号 position

[英]Saving and Restoring caret position for contentEditable div

I have a contentEditable div, the innerHTML of which can be updated through AJAX while editing.我有一个contentEditable div,它的innerHTML可以在编辑时通过 AJAX 进行更新。 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).问题是,当您更改 div 的内容时,它会将 cursor 移动到 div 的末尾(或根据浏览器失去焦点)。 What is a good cross-browser solution to store caret position before changing innerHTML and then to restore it?在更改innerHTML之前存储插入符号 position 然后恢复它的一个好的跨浏览器解决方案是什么?

back to 2016 :)回到 2016 年 :)
After I came across solutions here and they did not suit me, because my DOM was replaced completely after each typing.在我在这里遇到解决方案后,它们不适合我,因为每次输入后我的 DOM 都被完全替换了。 I've done more research and come with a simple solution that saves the cursor by character's position that works perfect for me.我做了更多的研究,并提供了一个简单的解决方案,可以按角色的位置保存光标,这对我来说是完美的。

The idea is very simple.这个想法很简单。

  1. find the length of characters before caret and save it.找到插入符号前的字符长度并保存。
  2. change the DOM.改变DOM。
  3. using TreeWalker to walk just on text nodes of context node and counting characters until we got the right text node and the position inside it使用TreeWalker仅在context node text nodes上行走并计算字符,直到我们获得正确的text node及其内部位置

Two edge case:两种边缘情况:

  1. content removed completely so there is no text node :内容完全删除,因此没有text node
    so : move the cursor to the start of the context node so :将光标移动到上下文节点的开头

  2. there is less content than the index pointed on :内容少于index指向的内容:
    so : move the cursor to the end of the last node so : 将光标移动到最后一个节点的末尾

 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>

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}

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.你可以使用Rangy ,我的跨浏览器范围和选择库。 It has a selection save and restore module that seems well-suited to your needs.它有一个选择保存和恢复模块,似乎非常适合您的需要。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM