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 node -
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>