Saving and Restoring caret position for contentEditable div

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.

  1. find the length of characters before caret and save it.
  2. change the 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

Two edge case:

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

  2. there 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>

Leave a Comment