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 topre
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>
A few wrinkles that I don't see being addressed in other answers:
- the element can contain multiple levels of child nodes (e.g. child nodes that have child nodes that have child nodes...)
- a selection can consist of different start and end positions (e.g. multiple chars are selected)
- the node containing a Caret start/end may not be either the element or its direct children
Here's a way to get start and end positions as offsets to the element's textContent value:
// node_walk: walk the element tree, stop when func(node) returns falsefunction node_walk(node, func) { var result = func(node); for(node = node.firstChild; result !== false && node; node = node.nextSibling) result = node_walk(node, func); return result;};// getCaretPosition: return [start, end] as offsets to elem.textContent that// correspond to the selected portion of text// (if start == end, caret is at given position and no text is selected)function getCaretPosition(elem) { var sel = window.getSelection(); var cum_length = [0, 0]; if(sel.anchorNode == elem) cum_length = [sel.anchorOffset, sel.extentOffset]; else { var nodes_to_find = [sel.anchorNode, sel.extentNode]; if(!elem.contains(sel.anchorNode) || !elem.contains(sel.extentNode)) return undefined; else { var found = [0,0]; var i; node_walk(elem, function(node) { for(i = 0; i < 2; i++) { if(node == nodes_to_find[i]) { found[i] = true; if(found[i == 0 ? 1 : 0]) return false; // all done } } if(node.textContent && !node.firstChild) { for(i = 0; i < 2; i++) { if(!found[i]) cum_length[i] += node.textContent.length; } } }); cum_length[0] += sel.anchorOffset; cum_length[1] += sel.extentOffset; } } if(cum_length[0] <= cum_length[1]) return cum_length; return [cum_length[1], cum_length[0]];}
$("#editable").on('keydown keyup mousedown mouseup',function(e){ if($(window.getSelection().anchorNode).is($(this))){ $('#position').html('0') }else{ $('#position').html(window.getSelection().anchorOffset); } });
body{ padding:40px;}#editable{ height:50px; width:400px; border:1px solid #000;}#editable p{ margin:0; padding:0;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js"></script><div contenteditable="true" id="editable">move the cursor to see position</div><div>position : <span id="position"></span></div>