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

3

61

I have contentEditable element (can be p, div, ...) and I would like to get caret (cursor) position in it. I can normally achieve it with this piece of code:

var position = window.getSelection().getRangeAt(0).startOffset;

This works fine while the element contains just text. But when the element contains some HTML formatting, the returned position is relative to caret position within included HTML element.

Let's assume contents of contentEditable element is this:

AB<b>CD</b>EF

If caret is inside <b></b>, let's say between C and D, the returned position with above code is 1 instead of 3 (counted from the begining of the contentEditable element's content)

Can anybody come up with solution to this ?

Alacrity answered 22/1, 2011 at 12:41 Comment(2)
If you want a character offset within the editable element, could I ask why? There is probably a better way to achieve what you want.Kegan
I have own WYSIWYG editor and it intentionally behaves little different than all common editors. Each <p> is contentEditable enabled. Now I am trying to solve problem when user wants to move from one paragraph to another just by using arrow keys. So I need to detect where in the paragraph is the caret, so I can reposition it according to pressed arrow key.Alacrity
K
54

UPDATE

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

https://mcmap.net/q/83470/-get-a-range-39-s-start-and-end-offset-39-s-relative-to-its-parent-container

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;
}
Kegan answered 22/1, 2011 at 21:3 Comment(9)
Thanks for answer, it looks good, however it has a little bug. Could you please try to fix it as I've tried it with no luck. I've changed your code to display caret position inline and also it would react to keyup event. The code works well, but it returns wrong result when caret is at the start or the end of tag. Try it yourself, just click in contentEditable div and keep pressing right arrow key to move caret. jsfiddle.net/2YcaX/1Alacrity
@Frodik: Done. See jsfiddle.net/timdown/2YcaX/3. I've also updated the answer.Kegan
Thanks a lot Tim, now its flawless, exactly what I needed. Thank you again.Alacrity
@Frodik: Since I haven't mentioned this anywhere else, I'll mention it here: this answer won't work for IE <= 8. I haven't attempted to provide a solution for IE here because your original question was using window.getSelection(), which isn't supported in IE.Kegan
Yeah, I am aware of that. My webapp doesn't support IE <= 8, because it makes heavy use of HTML5 features. So there is no concern about that.Alacrity
First, thanks for this wonderful answer. But i have a problem. It doesn't count line breaks. If I put three line breaks at the end, the caret position I get through this function still remains same. Can you help me with that?Footfall
@varunvs: I've written a more complicated version of this for my Rangy library: code.google.com/p/rangy/wiki/TextRangeModuleKegan
I just need to get the carret position just so I restore it before inserting a link. Isn't Rangy a bit overkill ?Onym
@Ced: Yes. I'd use the code in the linked answer (https://mcmap.net/q/83470/-get-a-range-39-s-start-and-end-offset-39-s-relative-to-its-parent-container)Kegan
R
18

This is a very old post, but still one of the first results searching on Google, so maybe still useful. This works for me to get right position considering html tags and newlines as well (tested on Firefox):

function getCaretPosition (node) {
    var range = window.getSelection().getRangeAt(0),
        preCaretRange = range.cloneRange(),
        caretPosition,
        tmp = document.createElement("div");

    preCaretRange.selectNodeContents(node);
    preCaretRange.setEnd(range.endContainer, range.endOffset);
    tmp.appendChild(preCaretRange.cloneContents());
    caretPosition = tmp.innerHTML.length;
    return caretPosition;
}

It uses the cloneContents functionality in order to get the actual html and appends the documentfragment to a temporary div in order to get the html length.

Rhodie answered 24/10, 2017 at 4:43 Comment(1)
This works far better for me than the currently accepted answer (both the author's original and updated versions) for a contentEditable div with formatted HTML within.Trader
G
1

If you want to insert element then you could try to do something like this:

// Get range
var range = document.caretRangeFromPoint(event.clientX, event.clientY);
if (range)
  range.insertNode(elementWhichYouWantToAddToContentEditable);
Gaby answered 17/6, 2014 at 17:59 Comment(3)
looks like caretRangeFromPoint is Firefox specific; that's a shame.Chowchow
document.caretRangeFromPoint(x,y) with webkit and document.caretPositionFromPoint(x,y) with firefox? developer.mozilla.org/en-US/docs/Web/API/Document/…. Dunno does this work: gist.github.com/unicornist/ac997a15bc3211ba1235Gaby
This solution is now deprecated in 2022.Melchior

© 2022 - 2024 — McMap. All rights reserved.