Get Caret Index in Contenteditable Div Including Tags

Get contentEditable caret position

The following code assumes:

  • There is always a single text node within the editable <div> and no other nodes
  • The editable div does not have the CSS white-space property set to pre

If you need a more general approach that will work content with nested elements, try this answer:

https://stackoverflow.com/a/4812022/96100

Code:

function getCaretPosition(editableDiv) {  var caretPos = 0,    sel, range;  if (window.getSelection) {    sel = window.getSelection();    if (sel.rangeCount) {      range = sel.getRangeAt(0);      if (range.commonAncestorContainer.parentNode == editableDiv) {        caretPos = range.endOffset;      }    }  } else if (document.selection && document.selection.createRange) {    range = document.selection.createRange();    if (range.parentElement() == editableDiv) {      var tempEl = document.createElement("span");      editableDiv.insertBefore(tempEl, editableDiv.firstChild);      var tempRange = range.duplicate();      tempRange.moveToElementText(tempEl);      tempRange.setEndPoint("EndToEnd", range);      caretPos = tempRange.text.length;    }  }  return caretPos;}
#caretposition {  font-weight: bold;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script><div id="contentbox" contenteditable="true">Click me and move cursor with keys or mouse</div><div id="caretposition">0</div><script>  var update = function() {    $('#caretposition').html(getCaretPosition(this));  };  $('#contentbox').on("mousedown mouseup keydown keyup", update);</script>

get cursor position in contenteditable div with html-tags

This gives you what you want. It will give you the length of the html also.

https://jsfiddle.net/ko5Lhbeo/

Change this:

$('.msg').on('keyup',function(e){
$('.cursor').html(getCaretPosition(document.getElementById('msg')));
})

to this:

// Added DOMSubtreeModified event here to fire whenever any modifications to the sub tree
$('.msg').on('keyup DOMSubtreeModified',function(e) {
$('.cursor').html($('#msg').html().length);
})

Get caret (cursor) position in contentEditable area containing HTML content

UPDATE

I've written a simpler version of this that also works in IE < 9:

https://stackoverflow.com/a/4812022/96100

Old Answer

This is actually a more useful result than a character offset within the text of the whole document: the startOffset property of a DOM Range (which is what window.getSelection().getRangeAt() returns) is an offset relative to its startContainer property (which isn't necessarily always a text node, by the way). However, if you really want a character offset, here's a function that will do it.

Here's a live example: http://jsfiddle.net/timdown/2YcaX/

Here's the function:

function getCharacterOffsetWithin(range, node) {
var treeWalker = document.createTreeWalker(
node,
NodeFilter.SHOW_TEXT,
function(node) {
var nodeRange = document.createRange();
nodeRange.selectNode(node);
return nodeRange.compareBoundaryPoints(Range.END_TO_END, range) < 1 ?
NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
},
false
);

var charCount = 0;
while (treeWalker.nextNode()) {
charCount += treeWalker.currentNode.length;
}
if (range.startContainer.nodeType == 3) {
charCount += range.startOffset;
}
return charCount;
}

how to get the caret position of a contenteditable div which contains images

See Tim Down's answer on Get a range's start and end offset's relative to its parent container.

Try to use the function he has to get the selection index with nested elements like this:

function getCaretCharacterOffsetWithin(element) {    var caretOffset = 0;    var doc = element.ownerDocument || element.document;    var win = doc.defaultView || doc.parentWindow;    var sel;    if (typeof win.getSelection != "undefined") {        sel = win.getSelection();        if (sel.rangeCount > 0) {            var range = win.getSelection().getRangeAt(0);            var preCaretRange = range.cloneRange();            preCaretRange.selectNodeContents(element);            preCaretRange.setEnd(range.endContainer, range.endOffset);            caretOffset = preCaretRange.toString().length;        }    } else if ( (sel = doc.selection) && sel.type != "Control") {        var textRange = sel.createRange();        var preCaretTextRange = doc.body.createTextRange();        preCaretTextRange.moveToElementText(element);        preCaretTextRange.setEndPoint("EndToEnd", textRange);        caretOffset = preCaretTextRange.text.length;    }    return caretOffset;}
var update = function() { console.log(getCaretCharacterOffsetWithin(this));};$('#text').on("mousedown mouseup keydown keyup", update);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><div contenteditable="true" id="text">minubyv<img src="https://themeforest.net/images/smileys/happy.png" class="emojiText" />iubyvt</div>

How do you get and set the caret position in a contenteditable?

This seems to work for me but I've only tested it for my use cases.

GET

function getCaretIndex(win, contentEditable) {
var index = 0;
var selection = win.getSelection();
var textNodes = textNodesUnder(contentEditable);

for(var i = 0; i < textNodes.length; i++) {
var node = textNodes[i];
var isSelectedNode = node === selection.focusNode;

if(isSelectedNode) {
index += selection.focusOffset;
break;
}
else {
index += node.textContent.length;
}
}

return index;
}

SET

function setCaretIndex(win, contentEditable, newCaretIndex) {
var cumulativeIndex = 0;
var relativeIndex = 0;
var targetNode = null;

var textNodes = textNodesUnder(contentEditable);

for(var i = 0; i < textNodes.length; i++) {
var node = textNodes[i];

if(newCaretIndex <= cumulativeIndex + node.textContent.length) {
targetNode = node;
relativeIndex = newCaretIndex - cumulativeIndex;
break;
}

cumulativeIndex += node.textContent.length;
}

var range = win.document.createRange();
range.setStart(targetNode, relativeIndex);
range.setEnd(targetNode, relativeIndex);
range.collapse();

var sel = win.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}

REQUIRED HELPER

function textNodesUnder(node) { // https://stackoverflow.com/a/10730777/3245937
var all = [];
for (node=node.firstChild;node;node=node.nextSibling){
if (node.nodeType==3) {
all.push(node);
}
else {
all = all.concat(textNodesUnder(node));
}
}
return all;
}

TEST (just call this function)

It loops through the text in a contenteditable, sets the caret index, then reads it. Console output is: (setIndex | getIndex)

function testContentEditable() {
document.body.innerHTML = "<div contenteditable></div>"
var ce = document.querySelector("[contenteditable]");
ce.focus();
ce.innerHTML = "HELLO <span data-foo='true' style='text-decoration: underline;'><span style='color:red;'>WORLD</span> MY</span> NAME IS BOB";

var i = 0;
var intv = setInterval(function() {
if(i == ce.innerText.length) {
clearInterval(intv);
}

setCaretIndex(window, ce, i);

var currentIndex = getCaretIndex(window, ce);
console.log(i + " | " + currentIndex);

i++;
}, 100);
}











FIDDLE

function getCaretIndex(win, contentEditable) {
var index = 0;
var selection = win.getSelection();
var textNodes = textNodesUnder(contentEditable);

for(var i = 0; i < textNodes.length; i++) {
var node = textNodes[i];
var isSelectedNode = node === selection.focusNode;

if(isSelectedNode) {
index += selection.focusOffset;
break;
}
else {
index += node.textContent.length;
}
}

return index;
}

function setCaretIndex(win, contentEditable, newCaretIndex) {
var cumulativeIndex = 0;
var relativeIndex = 0;
var targetNode = null;

var textNodes = textNodesUnder(contentEditable);

for(var i = 0; i < textNodes.length; i++) {
var node = textNodes[i];

if(newCaretIndex <= cumulativeIndex + node.textContent.length) {
targetNode = node;
relativeIndex = newCaretIndex - cumulativeIndex;
break;
}

cumulativeIndex += node.textContent.length;
}

var range = win.document.createRange();
range.setStart(targetNode, relativeIndex);
range.setEnd(targetNode, relativeIndex);
range.collapse();

var sel = win.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}

function textNodesUnder(node) { // https://stackoverflow.com/a/10730777/3245937
var all = [];
for (node=node.firstChild;node;node=node.nextSibling){
if (node.nodeType==3) {
all.push(node);
}
else {
all = all.concat(textNodesUnder(node));
}
}
return all;
}

function testContentEditable() {
document.body.innerHTML = "<div contenteditable></div>"
var ce = document.querySelector("[contenteditable]");
ce.focus();
ce.innerHTML = "HELLO <span data-foo='true' style='text-decoration: underline;'><span style='color:red;'>WORLD</span> MY</span> NAME IS BOB";

var i = 0;
var intv = setInterval(function() {
if(i == ce.innerText.length) {
clearInterval(intv);
}

setCaretIndex(window, ce, i);

var currentIndex = getCaretIndex(window, ce);
console.log(i + " | " + currentIndex);

i++;
}, 100);
}
testContentEditable();

Set Caret Position in 'contenteditable' div that has children

So, I was experiencing the same issue and decided to write my own routine quickly, it walks through all the child nodes recursively and set the position.
Note how this takes a DOM node as argument, not a jquery object as your original post does

// Move caret to a specific point in a DOM element
function SetCaretPosition(el, pos){

// Loop through all child nodes
for(var node of el.childNodes){
if(node.nodeType == 3){ // we have a text node
if(node.length >= pos){
// finally add our range
var range = document.createRange(),
sel = window.getSelection();
range.setStart(node,pos);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
return -1; // we are done
}else{
pos -= node.length;
}
}else{
pos = SetCaretPosition(node,pos);
if(pos == -1){
return -1; // no need to finish the for loop
}
}
}
return pos; // needed because of recursion stuff
}

I hope this'll help you!



Related Topics



Leave a reply



Submit