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

Iva*_*van 71 javascript browser cursor dom-events

如果我有例如

<p> some long text </p>
Run Code Online (Sandbox Code Playgroud)

在我的HTML页面上,我怎么知道鼠标光标例如在'text'一词之上?

Dam*_*isa 41

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

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

例如

<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>
Run Code Online (Sandbox Code Playgroud)

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

jsFiddle例子

  • 或者你可以做`$(this).text().replace(/\b(\ w +)\ b/g,"<span> $ 1 </ span>")`而不是循环.这将正确处理所有空白字符. (5认同)

Eya*_*yal 39

我的其他答案仅适用于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);
}    
Run Code Online (Sandbox Code Playgroud)

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

  • 这是一段很好的代码,但在处理混合的textNodes和其他内联元素时会破坏它.出现这种情况有两种情况.1.带有换行符的文本节点将具有无意义的边界框.2.高度大于textNode行的内联元素可以重置范围的垂直位置.我认为应该可以通过从一开始就逐字符检查textNodes并通过假设texNodes永远不会高于他们之前的任何兄弟姐妹(但可能并非总是如此)来补偿垂直位置的随机重置来克服这些问题. (2认同)

Dra*_*kes 36

前言:

如果您有多个跨度和嵌套HTML,它们分隔单词(甚至单词中的单词),那么上述所有解决方案都将无法返回完整且正确的单词.

以下是赏金问题的一个例子:?</span>r?0??.如何正确归还?r?0???这些问题在2010年没有得到解决,所以我现在将提出两个解决方案(2015年).


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

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

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


pic 0

码:

$(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', ''); }
  );
});
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

解决方案1全文演示


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

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

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

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

例:

?</span>r?0??必须回归?r?0??,而?不是r?0??.

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

例:

??.)</p><p>?? 不应该回来 ??.)??


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

pic 1

码:

$(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();
  }
}
Run Code Online (Sandbox Code Playgroud)
.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;}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

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

解决方案2全文演示

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


Mat*_*att 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>
Run Code Online (Sandbox Code Playgroud)

  • 以下是关于jsfiddle的上述代码的演示:http://jsfiddle.net/5bT4B/ (2认同)

erw*_*man 9

这是一个在 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;
}
Run Code Online (Sandbox Code Playgroud)

我将过滤标点符号和正确处理带连字符的单词作为练习留给读者:)。


chr*_*isv 5

在当前的CSSOM View草案中有一个API :document.caretPositionFromPoint(x,y)

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


Lud*_*ltz 5

这是赏金的解决方案.

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

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

从document.caretRangeFromPoint 文档:

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

从document.caretPositionFromPoint 文档:

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

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

查看完整示例:

$(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;
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)