An extension of Julien's answer above. This copes with multiple lines. Needs a little tweaking, but seems to work. It finds the number of lines by getting the height of a start to end selection, and the height of a single-letter selection, dividing the two, and rounding. There are probably situations where that won't work, but for most purposes...
function getLineCount(node, range) {
if ((node) && (range)) {
range.setStart(node, 0);
range.setEnd(node, 1);
var r = range.getBoundingClientRect();
var h1 = r.bottom - r.top;
range.setEnd(node, node.length);
r = range.getBoundingClientRect();
return Math.round((r.bottom - r.top) / h1);
}
};
Here's a tweaked version of the above code, using the line-count routine above. It also copes a little better with selections within the node, but off to the right of the actual text. None of this is optimized, but we're in user-time here, so milliseconds likely aren't too important.
function getSelectionNodeInfo(x, y) {
var startRange = document.createRange();
window.getSelection().removeAllRanges();
window.getSelection().addRange(startRange);
// Implementation note: range.setStart offset is
// counted in number of child elements if any or
// in characters if there is no childs. Since we
// want to compute in number of chars, we need to
// get the node which has no child.
var elem = document.elementFromPoint(x, y);
console.log("ElementFromPoint: " + $(elem).attr('class'));
var startNode = (elem.childNodes.length>0?elem.childNodes[0]:elem);
var lines = getLineCount(startNode, startRange);
console.log("Lines: " + lines);
var startCharIndexCharacter = 0;
startRange.setStart(startNode, 0);
startRange.setEnd(startNode, 1);
var letterCount = startNode.length;
var rangeRect = startRange.getBoundingClientRect();
var rangeWidth = 0
if (lines>1) {
while ((rangeRect.bottom < y) && (startCharIndexCharacter < (letterCount-1))) {
startCharIndexCharacter++;
startRange.setStart(startNode, startCharIndexCharacter);
startRange.setEnd(startNode, startCharIndexCharacter + 1);
rangeRect = startRange.getBoundingClientRect();
rangeWidth = rangeRect.right - rangeRect.left
}
}
while (rangeRect.left < (x-(rangeWidth/2)) && (startCharIndexCharacter < (letterCount))) {
startCharIndexCharacter++;
startRange.setStart(startNode, startCharIndexCharacter);
startRange.setEnd(startNode, startCharIndexCharacter + ((startCharIndexCharacter<letterCount) ? 1 : 0));
rangeRect = startRange.getBoundingClientRect();
rangeWidth = rangeRect.right - rangeRect.left
}
return {node:startNode, offsetInsideNode:startCharIndexCharacter};
}