Several days ago (last year, actually) I started researching the problem of implementing a browser-based rich text editor. Among other questions that need to be answered there is the following one: if we use a div element as the text editor or a text display widget (e.g. when capturing user input with a hidden textarea), how shall we track the current caret position?
As usual, there are two solutions: for Internet Explorer and everything else Both involve installing keyup and click event handlers:
<div id="test" contenteditable="true" onclick="getCursorPos();" onkeyup="getCursorPos();">Lots of text for testing</div>
As a rule, cursor position and the current selection are tightly wired together: when nothing is selected, the current selection begins and ends at the cursor position. So, we retrieve the selection range (in the code below, we assume that there is only one selection range in the window. I leave proper handling of the situation where multiple selection ranges exist as an exercise for the reader).
Now, the actual findNode function depends on the method you build the text editor and on your algorithmic skills. When you use a single long text node which includes all the text in the editor, it will be different from the case when every character is a separate text node. Besides, you are welcome to use optimized search algorithms if you are going to work with large documents. The important part here is that the anchorNode property references the first character in the selection, and you will only need to find its index among the other characters [1]. An example findNode() function is given below.
The cursor position and the selection display some dexterity in their mutual dependency. The quirk is that when you click on a character, the cursor is sometimes positioned by different math than the selection’s anchor. For instance, the cursor is positioned before the character if you click on its left 50% and after the character if you click on its right 50%, while if it is included in the selection or not depends on its geometry or the percentages are merely different. So, in order to reflect this, we add the selection object’s anchorOffset (actually, the selection length in characters) [2] to the value returned by the findNode function.
To make things work for Internet Explorer, we have to rely on a proprietary undocumented possibility. It appears that the second Unicode character in the bookmark of the selection range contains the value we need plus 11. Currently, I don’t know the meaning of this number, and whether its exact value changes depending on circumstances and can be calculated somehow.
function getCursorPos() {
var cursorPos;
if (window.getSelection) {
var selObj = window.getSelection();
var selRange = selObj.getRangeAt(0);
cursorPos = findNode(selObj.anchorNode.parentNode.childNodes, selObj.anchorNode) + selObj.anchorOffset;
/* FIXME the following works wrong in Opera when the document is longer than 32767 chars */
alert(cursorPos);
}
else if (document.selection) {
var range = document.selection.createRange();
var bookmark = range.getBookmark();
/* FIXME the following works wrong when the document is longer than 65535 chars */
cursorPos = bookmark.charCodeAt(2) - 11; /* Undocumented function [3] */
alert(cursorPos);
}
}
function findNode(list, node) {
for (var i = 0; i < list.length; i++) {
if (list[i] == node) {
return i;
}
}
return -1;
}
Well, that’s all. Solution tested in Firefox 3, Opera 9, Google Chrome, Safari 3.1 and IE7.
This entry was posted on Tuesday, January 6th, 2009 at 13:38 and is filed under Uncategorized. You can follow any responses to this entry through the RSS 2.0 feed.
You can leave a response, or trackback from your own site.
17 Responses to Finding cursor position in a contenteditable div
Hi,
when I use html tags inside the div the pointer doesnt show the correct position. Any ideas how to get it to show the right position?
I cant add HTML code here but you can add for example a span inside the div: span /span
I was able to find the cursor position in the contenteditable DIV but the function doesn’t return the cursor position if I have input(append) some html content(contenteditable=”false”) inside the DIV. The getCursorPOs function starts behaving randomly. Can you explain this behaviour.
Actually, I don’t know. Besides, I advise that you look at what Google has done for its Docs web app: they do not use a content-editable div; rather, they have reimplemented the whole document rendering from scratch in JavaScript. It’s a lot of hard work, but it appears to be the most cross-platform and stable way to do it.
How can this fellow find his solutions?…
[...] Finding cursor position in a contentEditable div. Up-to-date, cross-browser. [...]
ok I tried this and it works apart from it seems to always start at 43 at the 0 index???
Might be specific to your build of IE; I need more information in order to help.
Is there any other method for not to rely on IE’s proprietary/undocumented method??
None that I know of.
Hi,
when I use html tags inside the div the pointer doesnt show the correct position. Any ideas how to get it to show the right position?
I cant add HTML code here but you can add for example a span inside the div: span /span
Hi,
I was able to find the cursor position in the contenteditable DIV but the function doesn’t return the cursor position if I have input(append) some html content(contenteditable=”false”) inside the DIV. The getCursorPOs function starts behaving randomly. Can you explain this behaviour.
Thanks in advance.
Hey I am able to get the caret position but how do I set it back after changing innerHTML?
Great, This is the shortest way to get caret position I’ve seen.
How do you use the acquired position to set cursor position ?
Hi,
Thanks for the post. How can we set a cursor position for an editable div ?
Actually, I don’t know. Besides, I advise that you look at what Google has done for its Docs web app: they do not use a content-editable div; rather, they have reimplemented the whole document rendering from scratch in JavaScript. It’s a lot of hard work, but it appears to be the most cross-platform and stable way to do it.
Thank you for this post. Funny how the universe gives you what you need.
I’ve just had the same problem, and with combining some different stuff i’ve come to this end.
(testen in chrome, ff, ie)
// get pos
function getPos() {
// vars laden
var obj = O("content");
var select_start = 0;
var select_end = 0;
// actief maken
obj.focus();
// positie bepalen
if (window.getSelection) { // ff-chr
// obj maken
var sel = window.getSelection();
select_start = sel.focusOffset;
select_end = sel.anchorOffset;
}
else if (document.selection) {// ie
// vars laden
var range = document.selection.createRange();
var range2 = range.duplicate();
if (range.text != '') {// iets geselecteerd
// range + dummy maken / bewerken
range2.moveToElementText(obj);
range2.setEndPoint('EndToEnd', range);
// rekenen
select_start = range2.text.length - range.text.length;
select_end = begin + range.text.length;
} else {// niets geselecteerd
// kopieren
range2.moveToElementText(obj);
var Positie = -1;
while(range2.inRange(range)) {
range2.moveStart('character');
Positie++;
}
select_start = Positie;
select_end = Positie;
}
}
// returnen
return (new Array(select_start, select_end));
}
All of your scripts have a bug, they don’t return the good number if it encouters a linebreak.
Which browsers did you test them against? P.S. Patches are welcome, I’ve long since abandoned this whole thing.
it definitely happens on the newest chrome. Function can’t return the correct value when there is a line break