私の答えは、ティム・ダウンのソリューションから導き出されました。ティムに感謝します!
ただし、IE で作業する場合、Tim のソリューションには 2 つの問題があります。(1) 計算されたオフセットが正しくない、(2) 複雑すぎる、大量のコード。
以下の私のデモンストレーションを参照してください。
問題 1 については、テキストの最後の行あたり、たとえば「肩ロース ロース シャンクル ターダッケン シャンク カウ。ベーコン ボール チップ サーロイン ハム。」のどこかをクリックすると、オフセットの計算が IE と異なることがわかります (オリジナルソリューション) および IE メソッド 2 (私のソリューション)。また、IE の方法 2 (私の解決策) と Chrome、Firefox の結果は同じです。
私の解決策もはるかに簡単です。トリックは、TextRange を使用して絶対 X/Y 位置で選択を行った後、document.getSelection() を呼び出して IHTMLSelection のタイプを取得することです。これは IE<9 では機能しませんが、問題がなければ、この方法ははるかに簡単です。もう 1 つの注意点は、IE の場合、メソッドの副作用 (元のメソッドと同じ) は選択の変更 (つまり、ユーザーの元の選択が失われる) です。
// Internet Explorer method 2
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>";
}
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// REF: http://stackoverflow.com/questions/3127369/how-to-get-selected-textnode-in-contenteditable-div-in-ie
function getChildIndex(node) {
var i = 0;
while( (node = node.previousSibling) ) {
i++;
}
return i;
}
// All this code just to make this work with IE, OTL
// REF: http://stackoverflow.com/questions/3127369/how-to-get-selected-textnode-in-contenteditable-div-in-ie
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;
// Move the working range through the container's children, starting at
// the end and working backwards, until the working range reaches or goes
// past the boundary we're interested in
do {
containerElement.insertBefore(workingNode, workingNode.previousSibling);
workingRange.moveToElementText(workingNode);
} while ( (comparison = workingRange.compareEndPoints(
workingComparisonType, textRange)) > 0 && workingNode.previousSibling);
// We've now reached or gone past the boundary of the text range we're
// interested in so have identified the node we want
boundaryNode = workingNode.nextSibling;
if (comparison == -1 && boundaryNode) {
// This must be a data node (text, comment, cdata) since we've overshot.
// The working range is collapsed at the start of the node containing
// the text range's boundary, so we move the end of the working range
// to the boundary point and measure the length of its text to get
// the boundary's offset within the node
workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
boundaryPosition = {
node: boundaryNode,
offset: workingRange.text.length
};
} else {
// We've hit the boundary exactly, so this must be an element
boundaryPosition = {
node: containerElement,
offset: getChildIndex(workingNode)
};
}
// Clean up
workingNode.parentNode.removeChild(workingNode);
return boundaryPosition;
}
function onClick(event) {
var elt = document.getElementById('info');
elt.innerHTML = "";
var textNode;
var offset;
// Internet Explorer
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>";
}
// Internet Explorer method 2
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>";
}
// Firefox, Safari
// REF: https://developer.mozilla.org/en-US/docs/Web/API/Document/caretPositionFromPoint
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>";
// Chrome
// REF: https://developer.mozilla.org/en-US/docs/Web/API/document/caretRangeFromPoint
}
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>