如何使用JavaScript获取光标下的单词?

时间:2010-03-15 00:27:28

标签: javascript browser cursor dom-events

如果我有例如

<p> some long text </p>

在我的HTML页面上,如何知道鼠标光标位于“text”一词的上方?

12 个答案:

答案 0 :(得分:40)

除了另外两个答案之外,您可以使用jQuery(或通常是javascript)将段落分成跨度。

这样,您就不需要考虑在单词周围输出文本了。让你的javascript为你做。

e.g。

<p>Each word will be wrapped in a span.</p>
<p>A second paragraph here.</p>
Word: <span id="word"></span>

<script type="text/javascript">
    $(function() {
        // wrap words in spans
        $('p').each(function() {
            var $this = $(this);
            $this.html($this.text().replace(/\b(\w+)\b/g, "<span>$1</span>"));
        });

        // bind to each span
        $('p span').hover(
            function() { $('#word').text($(this).css('background-color','#ffff66').text()); },
            function() { $('#word').text(''); $(this).css('background-color',''); }
        );
    });
</script>

请注意,上述代码虽然有效,但会删除段落标记中的任何html。

jsFiddle example

答案 1 :(得分:38)

我的其他答案仅适用于Firefox。这个答案适用于Chrome。 (也可能在Firefox中工作,我不知道。)

function getWordAtPoint(elem, x, y) {
  if(elem.nodeType == elem.TEXT_NODE) {
    var range = elem.ownerDocument.createRange();
    range.selectNodeContents(elem);
    var currentPos = 0;
    var endPos = range.endOffset;
    while(currentPos+1 < endPos) {
      range.setStart(elem, currentPos);
      range.setEnd(elem, currentPos+1);
      if(range.getBoundingClientRect().left <= x && range.getBoundingClientRect().right  >= x &&
         range.getBoundingClientRect().top  <= y && range.getBoundingClientRect().bottom >= y) {
        range.expand("word");
        var ret = range.toString();
        range.detach();
        return(ret);
      }
      currentPos += 1;
    }
  } else {
    for(var i = 0; i < elem.childNodes.length; i++) {
      var range = elem.childNodes[i].ownerDocument.createRange();
      range.selectNodeContents(elem.childNodes[i]);
      if(range.getBoundingClientRect().left <= x && range.getBoundingClientRect().right  >= x &&
         range.getBoundingClientRect().top  <= y && range.getBoundingClientRect().bottom >= y) {
        range.detach();
        return(getWordAtPoint(elem.childNodes[i], x, y));
      } else {
        range.detach();
      }
    }
  }
  return(null);
}    

在您的mousemove处理程序中,调用getWordAtPoint(e.target, e.x, e.y);

答案 2 :(得分:33)

序言:

如果你有多个跨度和嵌套的HTML分隔单词(甚至是单词中的字符),那么上述所有解决方案都无法返回完整正确的单词。

以下是赏金问题中的一个示例:Х</span>rт0съ。如何正确返回Хrт0съ?这些问题在2010年没有得到解决,所以我现在将提出两个解决方案(2015年)。

解决方案1 ​​ - 剥离内部标签,围绕每个完整单词包围:

一种解决方案是删除段落中的span标记,但保留其文本。因此,分词和短语作为常规文本连接在一起。每个单词都是通过空格分割(不仅仅是空格)找到的,这些单词用跨度包装,可以单独访问。

在演示中,您可以突出显示整个单词,从而获得整个单词的文本。

pic 0

<强>代码:

&#13;
&#13;
$(function() {
  // Get the HTML in #hoverText - just a wrapper for convenience
  var $hoverText = $("#hoverText");

  // Replace all spans inside paragraphs with their text
  $("p span", $hoverText).each(function() {
    var $this = $(this);
    var text = $this.text(); // get span content
    $this.replaceWith(text); // replace all span with just content
  });

  // Wrap words in spans AND preserve the whitespace
  $("p", $hoverText).each(function() {
    var $this = $(this);
    var newText = $this.text().replace(/([\s])([^\s]+)/g, "$1<span>$2</span>");
    newText = newText.replace(/^([^\s]+)/g, "<span>$1</span>");
    $this.empty().append(newText);
  });

  // Demo - bind hover to each span
  $('#hoverText span').hover(
    function() { $(this).css('background-color', '#ffff66'); },
    function() { $(this).css('background-color', ''); }
  );
});
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="hoverText">
  <p><span class="kinovar"><span id="selection_index3337" class="selection_index"></span>По f7-мъ часЁ твори1тъ сщ7eнникъ начaло съ кади1ломъ и3 со свэщeю, цrкимъ двeремъ tвeрзєннымъ, и3 поeтъ: Х</span>rт0съ воскRсе: <span class="kinovar">со 
стіхи2. И# по стісёхъ pал0мъ: Б</span>лгcви2 душE моS гDа: <span class="kinovar">И# є3ктеніA. Тaже каfjсма nбhчнаz.</span>
  </p>
</div>
&#13;
&#13;
&#13;

Solution 1 full-text demo

解决方案2 - 插入符号检查和DOM遍历:

这是一个更复杂的解决方案。它是一种使用节点遍历的算法解决方案,可准确捕获文本节点中光标下的完整且正确的单词。

通过检查插入位置(使用caretPositionFromPointcaretRangeFromPoint,将该想法归功于@chrisv)找到临时字。这可能是也可能不是完整的词。

然后对其进行分析以查看它是否位于其文本节点的任一边缘(开始或结束)。如果是,则检查前一个文本节点或下一个文本节点以查看是否应该将其连接以使该单词片段更长。

示例:

Х</span>rт0съ必须返回Хrт0съ,而不是Хrт0съ

遍历DOM树以获取下一个非屏障文本节点。如果两个单词片段由<p>或其他障碍标记分隔,则它们不相邻,因此不属于同一个单词。

示例:

њб.)</p><p>Во不应该返回њб.)Во

在演示中,左浮动div是光标下的单词。右浮动div如果可见,则显示边界上的单词是如何形成的。其他标签可以安全地与此解决方案中的文本内联。

pic 1

<强>代码:

&#13;
&#13;
$(function() {
  // Get the HTML in #hoverText - just a wrapper for convenience
  var $hoverText = $("#hoverText");

  // Get the full word the cursor is over regardless of span breaks
  function getFullWord(event) {
     var i, begin, end, range, textNode, offset;
    
    // Internet Explorer
    if (document.body.createTextRange) {
       try {
         range = document.body.createTextRange();
         range.moveToPoint(event.clientX, event.clientY);
         range.select();
         range = getTextRangeBoundaryPosition(range, true);
      
         textNode = range.node;
         offset = range.offset;
       } catch(e) {
         return ""; // Sigh, IE
       }
    }
    
    // Firefox, Safari
    // REF: https://developer.mozilla.org/en-US/docs/Web/API/Document/caretPositionFromPoint
    else if (document.caretPositionFromPoint) {
      range = document.caretPositionFromPoint(event.clientX, event.clientY);
      textNode = range.offsetNode;
      offset = range.offset;

      // Chrome
      // REF: https://developer.mozilla.org/en-US/docs/Web/API/document/caretRangeFromPoint
    } else if (document.caretRangeFromPoint) {
      range = document.caretRangeFromPoint(event.clientX, event.clientY);
      textNode = range.startContainer;
      offset = range.startOffset;
    }

    // Only act on text nodes
    if (!textNode || textNode.nodeType !== Node.TEXT_NODE) {
      return "";
    }

    var data = textNode.textContent;

    // Sometimes the offset can be at the 'length' of the data.
    // It might be a bug with this 'experimental' feature
    // Compensate for this below
    if (offset >= data.length) {
      offset = data.length - 1;
    }

    // Ignore the cursor on spaces - these aren't words
    if (isW(data[offset])) {
      return "";
    }

    // Scan behind the current character until whitespace is found, or beginning
    i = begin = end = offset;
    while (i > 0 && !isW(data[i - 1])) {
      i--;
    }
    begin = i;

    // Scan ahead of the current character until whitespace is found, or end
    i = offset;
    while (i < data.length - 1 && !isW(data[i + 1])) {
      i++;
    }
    end = i;

    // This is our temporary word
    var word = data.substring(begin, end + 1);

    // Demo only
    showBridge(null, null, null);

    // If at a node boundary, cross over and see what 
    // the next word is and check if this should be added to our temp word
    if (end === data.length - 1 || begin === 0) {

      var nextNode = getNextNode(textNode);
      var prevNode = getPrevNode(textNode);

      // Get the next node text
      if (end == data.length - 1 && nextNode) {
        var nextText = nextNode.textContent;

        // Demo only
        showBridge(word, nextText, null);

        // Add the letters from the next text block until a whitespace, or end
        i = 0;
        while (i < nextText.length && !isW(nextText[i])) {
          word += nextText[i++];
        }

      } else if (begin === 0 && prevNode) {
        // Get the previous node text
        var prevText = prevNode.textContent;

        // Demo only
        showBridge(word, null, prevText);

        // Add the letters from the next text block until a whitespace, or end
        i = prevText.length - 1;
        while (i >= 0 && !isW(prevText[i])) {
          word = prevText[i--] + word;
        }
      }
    }
    return word;
  }

  // Return the word the cursor is over
  $hoverText.mousemove(function(e) {
    var word = getFullWord(e);
    if (word !== "") {
      $("#result").text(word);
    }
  });
});

// Helper functions

// Whitespace checker
function isW(s) {
  return /[ \f\n\r\t\v\u00A0\u2028\u2029]/.test(s);
}

// Barrier nodes are BR, DIV, P, PRE, TD, TR, ... 
function isBarrierNode(node) {
  return node ? /^(BR|DIV|P|PRE|TD|TR|TABLE)$/i.test(node.nodeName) : true;
}

// Try to find the next adjacent node
function getNextNode(node) {
  var n = null;
  // Does this node have a sibling?
  if (node.nextSibling) {
    n = node.nextSibling;

    // Doe this node's container have a sibling?
  } else if (node.parentNode && node.parentNode.nextSibling) {
    n = node.parentNode.nextSibling;
  }
  return isBarrierNode(n) ? null : n;
}

// Try to find the prev adjacent node
function getPrevNode(node) {
  var n = null;

  // Does this node have a sibling?
  if (node.previousSibling) {
    n = node.previousSibling;

    // Doe this node's container have a sibling?
  } else if (node.parentNode && node.parentNode.previousSibling) {
    n = node.parentNode.previousSibling;
  }
  return isBarrierNode(n) ? null : n;
}

// 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;
}

// DEMO-ONLY code - this shows how the word is recombined across boundaries
function showBridge(word, nextText, prevText) {
  if (nextText) {
    $("#bridge").html("<span class=\"word\">" + word + "</span>  |  " + nextText.substring(0, 20) + "...").show();
  } else if (prevText) {
    $("#bridge").html("..." + prevText.substring(prevText.length - 20, prevText.length) + "  |  <span class=\"word\">" + word + "</span>").show();
  } else {
    $("#bridge").hide();
  }
}
&#13;
.kinovar { color:red; font-size:20px;}.slavic { color: blue;}#result {top:10px;left:10px;}#bridge { top:10px; right:80px;}.floater { position: fixed; background-color:white; border:2px solid black; padding:4px;}.word { color:blue;}
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <div id="bridge" class="floater"></div> <div id="result" class="floater"></div> <div id="hoverText"><p><span class="kinovar"><span id="selection_index3337" class="selection_index"></span>По f7-мъ часЁ твори1тъ сщ7eнникъ начaло съ кади1ломъ и3 со свэщeю, цrкимъ двeремъ tвeрзєннымъ, и3 поeтъ: Х</span>rт0съ воскRсе: <span class="kinovar">со стіхи2. И# по стісёхъ pал0мъ: Б</span>лгcви2 душE моS гDа: <span class="kinovar">И# є3ктеніA. Тaже каfjсма nбhчнаz.</span></p><div class="slavic"> <input value="Works around other tags!"><p><span id="selection_index3737" class="selection_index"></span>(л. рo7з њб.)</p><p><span class="kinovar"><span id="selection_index3738" class="selection_index"></span>Во вт0рникъ вeчера</span> </p><p><span class="kinovar"><span id="selection_index3739" class="selection_index"></span>tдaніе прaздника пaсхи.</span></p><p><span class="kinovar"><span id="selection_index3740" class="selection_index"></span>По f7-мъ часЁ твори1тъ сщ7eнникъ начaло съ кади1ломъ и3 со свэщeю, цrкимъ двeремъ tвeрзєннымъ, и3 поeтъ: Х</span>rт0съ воскRсе: <span class="kinovar">со стіхи2. И# по стісёхъ pал0мъ: Б</span>лгcви2 душE моS гDа: <span class="kinovar">И# є3ктеніA. Тaже каfjсма nбhчнаz.<input value="Works around inline tags too"></span></p><p><span class="kinovar"><span id="selection_index3741" class="selection_index"></span>На ГDи воззвaхъ: поeмъ стіхи6ры самоглaсны, слэпaгw, на ѕ7. Глaсъ в7:</span></p></div>
&#13;
&#13;
&#13;

注意:我冒昧地将样式应用于示例HTML中的span标记,以阐明文本节点边框的位置。)

Solution 2 full-text demo

(到目前为止在Chrome和IE中工作。对于IE,来自IERange的方法必须用作跨浏览器兼容性的垫片)

答案 3 :(得分:11)

据我所知,你做不到。

我唯一能想到的就是将每个单词放在他们自己的元素中,然后将鼠标悬停在那些元素上。

<p><span>Some</span> <span>long</span> <span>text</span></p>

<script>
$(document).ready(function () {
  $('p span').bind('mouseenter', function () {
    alert($(this).html() + " is what you're currently hovering over!");
  });
});
</script>

答案 4 :(得分:5)

目前CSSOM View draft中有一个API:document.caretPositionFromPoint(x,y)

但是,您必须检查哪个浏览器支持此功能。 Firefox 7似乎根本不支持它,而bug报告表明Firefox 9会支持它。 Chrome 14支持caretRangeFromPoint(x,y),它基本上是相同的,但来自较旧的CSSOM草稿。

答案 5 :(得分:5)

这是赏金的解决方案。

根据chrisv的建议,您可以使用document.caretPositionFromPoint(chrome)或document.caretRangeFromPoint(Firefox)。 我认为这个解决方案可以更好地回答您的问题,因为它不会改变您的文本或DOM。

此函数返回鼠标光标下的单词而不更改DOM:

来自document.caretPositionFromPoint documentation

  

Document接口的caretRangeFromPoint()方法返回指定坐标下文档片段的Range对象。

来自$(function () { function getWordUnderCursor(event) { var range, textNode, offset; if (document.body.createTextRange) { // Internet Explorer try { range = document.body.createTextRange(); range.moveToPoint(event.clientX, event.clientY); range.select(); range = getTextRangeBoundaryPosition(range, true); textNode = range.node; offset = range.offset; } catch(e) { return ""; } } else if (document.caretPositionFromPoint) { // Firefox range = document.caretPositionFromPoint(event.clientX, event.clientY); textNode = range.offsetNode; offset = range.offset; } else if (document.caretRangeFromPoint) { // Chrome range = document.caretRangeFromPoint(event.clientX, event.clientY); textNode = range.startContainer; offset = range.startOffset; } //data contains a full sentence //offset represent the cursor position in this sentence var data = textNode.data, i = offset, begin, end; //Find the begin of the word (space) while (i > 0 && data[i] !== " ") { --i; }; begin = i; //Find the end of the word i = offset; while (i < data.length && data[i] !== " ") { ++i; }; end = i; //Return the word under the mouse cursor return data.substring(begin, end); } //Get the HTML in a div #hoverText and detect mouse move on it var $hoverText = $("#hoverText"); $hoverText.mousemove(function (e) { var word = getWordUnderCursor(e); //Show the word in a div so we can test the result if (word !== "") $("#testResult").text(word); }); }); // This code make it works with IE // REF: https://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; } documentation

  

此方法用于根据两个坐标检索文档中的插入符位置。返回CaretPosition,其中包含找到的DOM节点和该节点中的字符偏移量。

这两个函数略有不同,但它们都返回包含文本的节点和此文本中光标的偏移量。所以很容易在鼠标下面得到这个词。

参见完整示例:

&#13;
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> 
<b><div id="testResult"></div></b>
<div id="hoverText">   <p><span class="kinovar"><span id="selection_index3337" class="selection_index"></span>По f7-мъ часЁ твори1тъ сщ7eнникъ начaло съ кади1ломъ и3 со свэщeю, цrкимъ двeремъ tвeрзєннымъ, и3 поeтъ: Х</span>rт0съ воскRсе: <span class="kinovar">со стіхи2. И# по стісёхъ pал0мъ: Б</span>лгcви2 душE моS гDа: <span class="kinovar">И# є3ктеніA. Тaже каfjсма nбhчнаz.</span> </p> <div class="slavic"><p><span id="selection_index3737" class="selection_index"></span>(л. рo7з њб.)</p> <p><span class="kinovar"><span id="selection_index3738" class="selection_index"></span>Во вт0рникъ вeчера</span></p> <p><span class="kinovar"><span id="selection_index3739" class="selection_index"></span>tдaніе прaздника пaсхи.</span></p><p><span class="kinovar"><span id="selection_index3740" class="selection_index"></span>По f7-мъ часЁ твори1тъ сщ7eнникъ начaло съ кади1ломъ и3 со свэщeю, цrкимъ двeремъ tвeрзєннымъ, и3 поeтъ: Х</span>rт0съ воскRсе: <span class="kinovar">состіхи2. И# по стісёхъ pал0мъ: Б</span>лгcви2 душE моS гDа: <span class="kinovar">И# є3ктеніA. Тaже каfjсма nбhчнаz.</span> </p><p><span class="kinovar"><span id="selection_index3741" class="selection_index"></span>На ГDи воззвaхъ: поeмъ стіхи6ры самоглaсны, слэпaгw, на ѕ7. Глaсъ в7:</span> </p><p><span class="kinovar"><span id="selection_index3742" class="selection_index"></span>С</span>лэпhй роди1выйсz, въ своeмъ п0мыслэ глаг0лаше: є3дA ѓзъ грBхъ рaди роди1тельныхъ роди1хсz без8 џчію; (л. рo7и) є3дA ѓзъ за невёріе kзhкwвъ роди1хсz во њбличeніе; не домышлsюсz вопрошaти: когдA н0щь, когдA дeнь; не терпи1та ми2 н0зэ кaменнагw претыкaніz, не ви1дэхъ сlнца сіsюща, нижE во џбразэ менE создaвшагw. но молю1 ти сz хrтE б9е, при1зри на мS, и3 поми1луй мS.</p></div></div>
&#13;
{{1}}
&#13;
&#13;
&#13;

答案 6 :(得分:4)

这是一个在大多数情况下适用于Chrome的简单解决方案:

function getWordAtPoint(x, y) {
  var range = document.caretRangeFromPoint(x, y);

  if (range.startContainer.nodeType === Node.TEXT_NODE) {
    range.expand('word');
    return range.toString().trim();
  }

  return null;
}

我将过滤掉标点符号并妥善处理连字符作为练习给读者:)。

答案 7 :(得分:3)

哎呀!这是ho!

简单直观且 whitout Jquery 或任何其他框架 小提琴:https://jsfiddle.net/703c96dr/

它将在每个单词上添加跨度并添加onmouseover和onomouseout函数。我可以创建一个简单的类来使其更有用,但代码非常简单,任何人都可以编辑和使用。

<p>This is my text example of word highlighting or, if you want, word hovering</p>
<p>This is another text example of word highlighting or, if you want, word hovering</p>

简单代码

function onmouseoverspan(){
    this.style.backgroundColor = "red";
}
function onmouseoutspan(){
    this.style.backgroundColor = "transparent";
}
var spans,p = document.getElementsByTagName("p");
for(var i=0;i<p.length;i++) {
    if(p[i]==undefined) continue;
    p[i].innerHTML = p[i].innerHTML.replace(/\b(\w+)\b/g, "<span>$1</span>");
    spans = p[i].getElementsByTagName("span")
    for(var a=0;a<spans.length;a++) {
        spans[a].onmouseover = onmouseoverspan;
        spans[a].onmouseout = onmouseoutspan;
    }
}

答案 8 :(得分:2)

您可能需要拆分该段落,以便每个单词都包含在其自己的单独的&lt; span&gt;内。元素,然后为每个元素添加onmouseover个事件属性。

..我认为你的意思是“&lt; p&gt;一些长文&lt; / p&gt;”;反斜杠不是HTML的一部分。

答案 9 :(得分:1)

在Firefox中,您可以挂钩mousemove事件。回调有一个参数,e。在回调中,执行以下操作:

var range = HTTparent.ownerDocument.createRange();
range.selectNode(e.rangeParent);
var str = range.toString();
range.detach();

现在str有鼠标结束的整个文本。 e.rangeOffset是该字符串中mousepointer的位置。在你的情况下,str将是“一些长文本”,如果你超过“文本”中的“e”,e.rangeOffset将是11。

如果你在边缘,这个代码会有点混乱,例如当鼠标指针与文本在同一行但在结尾之后。要解决此问题,您需要检查您实际上是否在文本之上。这是测试:

if(e && e.rangeParent && e.rangeParent.nodeType == e.rangeParent.TEXT_NODE
   && e.rangeParent.parentNode == e.target)

此技术适用于Firefox。在Chrome中不起作用。

答案 10 :(得分:0)

&#13;
&#13;
function escapeHtml(unsafe) {
  return unsafe
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

// 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);
&#13;
#info {
  position: absolute;
  bottom: 0;
  background-color: cyan;
}
&#13;
<div class="parent">
  <div class="child">SPACE&nbsp;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>
&#13;
&#13;
&#13;

我的回答来自Drakes&#39; &#34;解决方案2 - 插入符号检查和DOM遍历&#34;。非常感谢Drakes指出这个解决方案!

然而,Drakes&#39;有两个问题。解决方案2在IE上工作时。 (1)计算的偏移量不正确,(2)过于复杂,代码很多。

here见我在JSFiddle上的演示。

对于问题1,如果你点击文本最后一行的某个地方,例如在&#34;肩猪扒shankle turducken shank cow的某处。培根球尖牛腩火腿。&#34;,您可以注意到偏移计算与IE(原始解决方案)和IE方法2(我的解决方案)不同。此外,IE方法2(我的解决方案)和Chrome,Firefox的结果是相同的。

我的解决方案也更简单。诀窍是,在使用TextRange在绝对X / Y位置进行选择后,通过调用document.getSelection()获得一种IHTMLSelection。这对IE&lt; 9不起作用,但如果你可以,那么这个方法要简单得多。另一个警告是,使用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>";
  }  

答案 11 :(得分:0)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body onclick="myfunc(event)">
    <span>hello </span><span> world </span>


    <script>
       function myfunc(event){

           console.log(event.target.innerHTML);
       }


    </script>
</body>
</html>