function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function getChildIndex(node) {
var i = 0;
while( (node = node.previousSibling) ) {
i++;
}
return i;
}
function getTextRangeBoundaryPosition(textRange, isStart) {
var workingRange = textRange.duplicate();
workingRange.collapse(isStart);
var containerElement = workingRange.parentElement();
var workingNode = document.createElement("span");
var comparison, workingComparisonType = isStart ?
"StartToStart" : "StartToEnd";
var boundaryPosition, boundaryNode;
do {
containerElement.insertBefore(workingNode, workingNode.previousSibling);
workingRange.moveToElementText(workingNode);
} while ( (comparison = workingRange.compareEndPoints(
workingComparisonType, textRange)) > 0 && workingNode.previousSibling);
boundaryNode = workingNode.nextSibling;
if (comparison == -1 && boundaryNode) {
workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
boundaryPosition = {
node: boundaryNode,
offset: workingRange.text.length
};
} else {
boundaryPosition = {
node: containerElement,
offset: getChildIndex(workingNode)
};
}
workingNode.parentNode.removeChild(workingNode);
return boundaryPosition;
}
function onClick(event) {
var elt = document.getElementById('info');
elt.innerHTML = "";
var textNode;
var offset;
if (document.body.createTextRange) {
elt.innerHTML = elt.innerHTML+("*************** IE **************<br/>");
range = document.body.createTextRange();
range.moveToPoint(event.clientX, event.clientY);
range.select();
range = getTextRangeBoundaryPosition(range, true);
textNode = range.node;
offset = range.offset;
elt.innerHTML = elt.innerHTML + "IE ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>";
}
if (document.body.createTextRange) {
elt.innerHTML = elt.innerHTML+("*************** IE, Method 2 **************<br/>");
range = document.body.createTextRange();
range.moveToPoint(event.clientX, event.clientY);
range.select();
var sel = document.getSelection();
textNode = sel.anchorNode;
offset = sel.anchorOffset;
elt.innerHTML = elt.innerHTML + "IE M2 ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>";
}
if (document.caretPositionFromPoint) {
elt.innerHTML = elt.innerHTML+("*************** Firefox, Safari **************<br/>");
range = document.caretPositionFromPoint(event.clientX, event.clientY);
textNode = range.offsetNode;
offset = range.offset;
elt.innerHTML = elt.innerHTML + "caretPositionFromPoint ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>";
}
if (document.caretRangeFromPoint) {
elt.innerHTML = elt.innerHTML+("*************** Chrome **************<br/>");
range = document.caretRangeFromPoint(event.clientX, event.clientY);
textNode = range.startContainer;
offset = range.startOffset;
elt.innerHTML = elt.innerHTML + "caretRangeFromPoint ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>";
}
}
document.addEventListener('click', onClick);
#info {
position: absolute;
bottom: 0;
background-color: cyan;
}
<div class="parent">
<div class="child">SPACE SPACE Bacon ipsum dolor amet <span>SPAN SPANTT SPOOR</span> meatball bresaola t-bone tri-tip brisket. Jowl pig picanha cupim SPAXE landjaeger, frankfurter spare ribs chicken. Porchetta jowl pancetta drumstick shankle cow spare ribs jerky
tail kevin biltong capicola brisket venison bresaola. Flank sirloin jowl andouille meatball venison salami ground round rump boudin turkey capicola t-bone. Sirloin filet mignon tenderloin beef, biltong doner bresaola brisket shoulder pork loin shankle
turducken shank cow. Bacon ball tip sirloin ham.
</div>
<div id="info">Click somewhere in the paragraph above</div>
</div>