Saving and Restoring caret position for contentEditable div
I have a content开发者_运维百科Editable
div, the innerHTML
of 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 innerHTML
and then to restore it?
back to 2016 :)
After I came across solutions here and they did not suit me, because my DOM was replaced completely after each typing. 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.
- find the length of characters before caret and save it.
- change the DOM.
- using
TreeWalker
to walk just ontext nodes
ofcontext node
and counting characters until we got the righttext node
and the position inside it
Two edge case:
content removed completely so there is no
text node
:
so: move the cursor to the start of the context nodethere is less content than the
index
pointed on :
so: move the cursor to the end of the last node
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/
Tested in chrome, FF, and IE10+ Allows you to change, delete and restore html while retaining caret position/selection.
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}
Update: I've ported Rangy's code to a standalone Gist:
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 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). The main advantage of Rangy is support for IE <= 8.
精彩评论