To get and set the caret position in an contenteditable element, I've tried the code from this answer, but the start & end position resets as you move into different text nodes.
<div contenteditable>012345<br><br><br>9012345</div>
So, I modified the code from this answer (by @TimDown) but it's still not quite counting the line breaks properly... In this demo, when I click after the 4
and press the right arrow three times, I'll see the start/end report as 5
, 6
, then 8
. Or, use the mouse to select from the 4
in the first row and continuing selecting to the right (see gif)
Here is the code (demo; even though it looks like it, jQuery is not being used)
function getCaret(el) {
let start, end;
const range = document.getSelection().getRangeAt(0),
preSelectionRange = range.cloneRange(),
postSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(el);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
postSelectionRange.selectNodeContents(el);
postSelectionRange.setEnd(range.endContainer, range.endOffset);
start = preSelectionRange.toString().length;
end = start + range.toString().length;
// count <br>'s and adjust start & end
if (start > 0) {
var node,
i = el.children.length;
while (i--) {
node = el.children[i];
if (node.nodeType === 1 && node.nodeName === 'BR') {
start += preSelectionRange.intersectsNode(el.children[i]) ? 1 : 0;
end += postSelectionRange.intersectsNode(el.children[i]) ? 1 : 0;
}
}
}
return {start, end};
}
The setCaret
function modification appears to be working properly (in this basic contenteditable example).
function setCaret(el, start, end) {
var node, i, nextCharIndex, sel,
charIndex = 0,
nodeStack = [el],
foundStart = false,
stop = false,
range = document.createRange();
range.setStart(el, 0);
range.collapse(true);
while (!stop && (node = nodeStack.pop())) {
// BR's aren't counted, so we need to increase the index when one
// is encountered
if (node.nodeType === 1 && node.nodeName === 'BR') {
charIndex++;
} else if (node.nodeType === 3) {
nextCharIndex = charIndex + node.length;
if (!foundStart && start >= charIndex && start <= nextCharIndex) {
range.setStart(node, start - charIndex);
foundStart = true;
}
if (foundStart && end >= charIndex && end <= nextCharIndex) {
range.setEnd(node, end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
sel = document.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
I could use some advice/help with the following issues:
- How do I properly count the
<br>
s? How do you count a
<br>
at the beginning (in this HTML example)?<div contenteditable><br>12345<br><br><br>9012345</div>
Include
<br>
's wrapped in a<div>
(in this HTML example) - I'll eventually get to this, but I didn't want to continue down this path and find out there is an easier method.<div contenteditable><div><br></div>12345<div><br></div><div><br></div><div><br></div>9012345</div>
I tried to replace the above code with
rangy
, but it doesn't appear to have a built-in method to get or set a range.
<br>
elements between as a single text node, which I would call a category error. – Oaken