Contenteditable DIV - how can I determine if the cursor is at the start or end of the content
I would use a similar approach to yours except using the toString()
method of Range
objects rather than cloneContents()
to avoid unnecessary cloning. Also, in IE < 9 (which doesn't support ranges), you can use a similar approach with the text
property of TextRange
.
Note that this will have issues when there are leading and/or trailing line breaks in the content because the toString()
method of a range works just like the textContent
property of a node and only considers text nodes, therefore not taking into account line breaks implied by <br>
or block elements. Also CSS is not taken into account: for example, text inside elements that are hidden via display: none
is included.
Here's an example:
Live demo: http://jsfiddle.net/YA3Pu/1/
Code:
function getSelectionTextInfo(el) {
var atStart = false, atEnd = false;
var selRange, testRange;
if (window.getSelection) {
var sel = window.getSelection();
if (sel.rangeCount) {
selRange = sel.getRangeAt(0);
testRange = selRange.cloneRange();
testRange.selectNodeContents(el);
testRange.setEnd(selRange.startContainer, selRange.startOffset);
atStart = (testRange.toString() == "");
testRange.selectNodeContents(el);
testRange.setStart(selRange.endContainer, selRange.endOffset);
atEnd = (testRange.toString() == "");
}
} else if (document.selection && document.selection.type != "Control") {
selRange = document.selection.createRange();
testRange = selRange.duplicate();
testRange.moveToElementText(el);
testRange.setEndPoint("EndToStart", selRange);
atStart = (testRange.text == "");
testRange.moveToElementText(el);
testRange.setEndPoint("StartToEnd", selRange);
atEnd = (testRange.text == "");
}
return { atStart: atStart, atEnd: atEnd };
}
This is how I ended up solving this. My proposed solution above worked sometimes but there were way to many edge cases, so I ended up considering how much text was before or after the cursor, and if that was 0 characters, then I was at the start or end:
handle_keydown = function(e) {
// Get the current cusor position
range = window.getSelection().getRangeAt(0)
// Create a new range to deal with text before the cursor
pre_range = document.createRange();
// Have this range select the entire contents of the editable div
pre_range.selectNodeContents(this);
// Set the end point of this range to the start point of the cursor
pre_range.setEnd(range.startContainer, range.startOffset);
// Fetch the contents of this range (text before the cursor)
this_text = pre_range.cloneContents();
// If the text's length is 0, we're at the start of the div.
at_start = this_text.textContent.length === 0;
// Rinse and repeat for text after the cursor to determine if we're at the end.
post_range = document.createRange();
post_range.selectNodeContents(this);
post_range.setStart(range.endContainer, range.endOffset);
next_text = post_range.cloneContents();
at_end = next_text.textContent.length === 0;
}
Still not entirely sure there are any other edge cases, as I'm not entirely sure how to unit test this, since it requires mouse interaction - there's probably a library to deal with this somewhere.
I figured out this pretty consistent and short method:
function isAtTextEnd() {
var sel = window.getSelection(),
offset = sel.focusOffset;
sel.modify ("move","forward","character");
if (offset == sel.focusOffset) return true;
else {
sel.modify ("move","backward","character");
return false;
}
}
The key: try to force move it one character forward - if it actually moved:
not at the end (move it one character back), if it didn't - it's at the end (no need to move back, it didn't move).
Implementing for start of text is the opposite, and is "left as an exercise for the reader"...
Cavities:
MDN marks
modify
as "Non-standard", though the compatibility table shows a pretty wide support (tested to work on the latest Chrome and Firefox, according to the table - not supported in Edge).
I tried using the more supportedextend()
for it - however, it seems that, weirdly, the extend does work even when at the end of text.If you check if after a user initiates a move of the caret (e.g. in a keyboard or mouse event handler) - you should handle cases where the check forces the caret to move in an unexpected way.