I have a contentEditable
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?
3 Answers
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>

- 7,713
- 6
- 52
- 57
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}

- 1,572
- 15
- 39
-
2This looks as though it's converting each selection range boundary to a path and back again. This is a great approach so long as the structure of the DOM is the same before and after the `innerHTML` changes, which isn't guaranteed to be true. – Tim Down Mar 31 '15 at 16:28
-
Is it possible to fix this code for multiple contenteditable divs? So that I can select, lets say, 1 of 3 div contenteditable, and then retrive the position of where I want to insert. – Gjert Jan 06 '16 at 13:35
-
3
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.

- 318,141
- 75
- 454
- 536
-
Fantastic. I had some trepidation about using a random library from some guy on SO, but it did what I wanted in 2 lines of code. Thanks! – thedayturns Aug 07 '11 at 22:05
-
3@thedayturns: That's the correct attitude to have, so I don't blame you :) I'm glad it helped. – Tim Down Aug 07 '11 at 22:13
-
@TimDown Does Rangy support multiple contenteditable divs? Like, saving position of caret over three different divs. Reason being, I want to use 1 editor for 3 different fields. – Gjert Jan 06 '16 at 14:22
-
I not sure how that approach can work if i replace completely the whole div's content – pery mimon Jul 20 '16 at 08:15
-
@perymimon: No, it wouldn't. I'd use a character offset-based approach instead in that case. – Tim Down Jul 20 '16 at 08:57
-
Hey Tim, sorry for asking here, but is there a standalone version of the save / restore module that will not require the Rangy core ? – Norman Mar 10 '19 at 06:27
-
I'm essentially looking for two functions: "save_range": to insert invisible span elements at current selection start and end (or one at caret). and "restore_range": that will change the spans back into a selection. nothing much more than that. – Norman Mar 10 '19 at 06:34
-
1@Norman: I ported Rangy's code to a standalone Gist: https://gist.github.com/timdown/244ae2ea7302e26ba932a43cb0ca3908. Obviously you can cut the selection stuff out if you like, but I left it in in case it was useful to anyone else. – Tim Down Mar 11 '19 at 11:44
of ? it still should work . that code build for richtext editor that change elements around the cursor when user writing – pery mimon Dec 13 '16 at 02:19
to display inline-block help a bit with line-break.
– Ambroise Rabier Oct 19 '21 at 13:40