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


77

如果我有

<p> some long text </p>

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


1
这是一个实时演示,基于Damovisa提供的源代码,如何使用JavaScript在光标下找到单词jsfiddle.net/5gyRx
Rubens Mariuzzo 2010年

2
@Ivan悬赏金为这个问题提供了新的答案。您可以考虑选择它(为了新用户的到来)。
user1122069 2015年

Answers:


43

除了另外两个答案之外,您还可以使用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>

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

jsFiddle示例


5
或者,您可以做$(this).text().replace(/\b(\w+)\b/g, "<span>$1</span>")而不是循环。这将正确处理所有空白字符。
Chetan S'3

@Chetan-谢谢你,我对正则表达式不是很好,所以我做了简单的方法:)我已经更新了它。
达莫维萨

我考虑了一下,但这是一个尴尬的解决方案(我是JavaScript的新手,所以我的方法比您的方法差得多)。感谢您的澄清。@Chetan-这是一个很好的解决方案。
伊万

我们将如何编辑它以便识别h1,h2,h3等标签,而不只是p标签?
idude

@idude您应该只能够用等替换第一个$('p')选择器$('p,h1,h2,h3')。同样,要使悬停工作,您需要将第二个选择器更改为$('p span,h1 span,h2 span,h3 span')
Damovisa 2014年

40

我的其他答案仅适用于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);


代码可以在iOS(6/7)上正常运行,但在Android 4.0.3中,getBoundingClientRect可以为null。因此添加:range.getBoundingClientRect()!= null作为第一个循环中的条件(获取left属性之前)。
雨果·洛格曼斯2013年

文档指出,“单词”的边界是空白字符。但是扩展似乎不适用于网址。有任何想法吗?
Adam Gotterer 2013年

@Eyal我发现您的代码在chrome中运行良好,而不是在Firefox中运行。
Nagaraju 2014年

2
这是一段不错的代码,但是在处理textNodes和其他内联元素时,它会中断。在两种情况下会出现这种情况。1.具有换行符的文本节点将具有废话边界框。2.高度大于textNode行的内联元素可以重置范围的垂直位置。我认为应该有可能通过从一开始就逐个字符地检查textNodes并通过假设texNode永远不会高于其先前的任何同级来补偿垂直位置的随机重置来克服这些问题(但这可能并不总是正确的)。
2014年

另外,while循环中的条件+1也是不必要的。textNode的最后一个字符开始于range.endOffset(结束于range.endOffset + 1)。因此,除非条件实际上while(currentPos < endPos)是最后一个字符,否则将永远不会对其进行测试。
2014年

39

前言:

如果您有多个跨度并且使用嵌套的HTML分隔单词(甚至单词中的字符),那么上述所有解决方案都将难以返回完整且正确的单词。

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


解决方案1-去除内部标签,将跨度包装在每个完整单词周围:

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

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


图片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', ''); }
  );
});
<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>

解决方案1全文演示


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

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

通过检查插入符号的位置可以找到一个临时单词(使用caretPositionFromPointcaretRangeFromPoint,将其表示为@chrisv)。这可能不是完整的词。

然后对其进行分析,以查看它是否在其文本节点的任一边缘(开头或结尾)。如果是,则检查前一个文本节点或后一个文本节点,看是否应该将其连接以延长此单词片段的长度。

例:

Х</span>rт0съ必须返回Хrт0съХ也不返回rт0съ

遍历DOM树以获得下一个无障碍文本节点。如果两个单词片段被某个<p>或其他障碍标签隔开,则它们不相邻,因此不是同一单词的一部分。

例:

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


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

图片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();
  }
}
.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;}
<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>

注意:我很随意将样式应用于示例HTML中的span标签,以阐明文本节点边界的位置。)

解决方案2全文演示

(到目前为止,可以在Chrome和IE中工作。对于IE,必须使用IERange的方法作为跨浏览器兼容性的填充程序)


在这种斯拉夫语编码中,{表示重音,因此我只想将一个单词算作空格中的所有内容,甚至是真正的标点符号(因为我自己会删除它们)。从技术上来说,答案并不令人满意,但是如果能最好地解决问题,我会选择。
user1122069 2015年

@ user1122069我发布了第二个解决方案,一个更好的解决方案,它使用DOM遍历并且也可以在IE中使用。它的速度很快,并且旨在对将来的HTML保持健壮。我喜欢这两种解决方案,但是这一解决方案没有按照您的要求使用span标签换行。
德雷克斯

谢谢。到目前为止工作完美。我将函数封装为一个对象,以使其与我的应用程序更好地协同工作。jsfiddle.net/ohaf4ytL/1我认为这对其他人也将非常有用。
user1122069 2015年

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>

2
这是上述代码在jsfiddle上的演示:jsfiddle.net/5bT4B
Anderson Green

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

我将过滤掉标点符号并正确处理带连字符的单词作为练习:)。


1
正是我所需的Chrome扩展程序。
chemamolins

@chemamolins这正是促使我想出这个食谱的原因:)。
erwaman

x / y坐标必须是event.clientX,而不是event.pageX。如果使用pageX,则在滚动页面并且鼠标位于初始视口坐标之外时,caretRangeFromPoint()将返回null。
tyshock

5

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

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


您的回答似乎适合我对该项目的赏识。只需要做一些工作即可真正找到从插入符号扩展的单词。原生范围扩展方法效果不佳。我可以自己研究这个问题,但是如果您可以提供与我的演示jsfiddle.net/ohaf4ytL一起使用的代码,那就太好了。
user1122069

@ user1122069我已经实现了此解决方案(stackoverflow.com/a/30606508/2576706)。是否符合您的需求?
Ludovic Feltz

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


“在#hoverText中获取HTML-只是为了方便使用包装器”-看起来很熟悉
Drakes

@Drakes我在这里阅读了所有答案,发现您的想法是将文本包装在div中,所以我保持相同的名字。但是之后的代码完全不同:)
Ludovic Feltz

@Drakes顺便说一句,我发现您的解决方案非常好。祝您好运;)
Ludovic Feltz

出于某种原因,“ textNode未定义”在IE中。
2015年

感谢Ludovic。正是我所需要的,它的运行非常出色。我仅将其用于Safari,并注意到即使单击页面上的任何空白,caretRangeFromPoint都将返回最接近的范围。似乎是一个长期存在的错误: bugs.webkit.org/show_bug.cgi?id=29249
empedocle

3

哎呀!ho!

如此简单,不需要Jquery或任何其他框架Fiddle: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;
    }
}

2

您可能需要对段落进行分解,以使每个单词都包含在其自己的单独的<span>元素内,然后添加 onmouseover事件属性。

..我认为您的意思是“ <p>一些长文本</ p>”;反斜杠不是HTML的一部分。


2

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

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

现在str拥有鼠标悬停时的全部文本。e.rangeOffset是鼠标指针在该字符串中的位置。在您的情况下,如果您位于“文本”中的“ e”上方,则str将为“一些长文本”,而e.rangeOffset将为11。

如果您位于空白处,则此代码会有些混乱,例如,当鼠标指针与文本在同一行上但在文本结尾之后时。要解决此问题,您需要检查自己是否位于文本顶部。这是测试:

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

此技术在Firefox中有效。在Chrome中不起作用。


0

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);
#info {
  position: absolute;
  bottom: 0;
  background-color: cyan;
}
<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>

我的答案来自Drakes的“解决方案2-插入符检查和DOM遍历”。非常感谢Drakes指出了该解决方案!

但是,在IE上工作时,Drakes的解决方案2存在两个问题。(1)计算出的偏移量是不正确的,并且(2)太复杂了,代码很多。

这里查看我在JSFiddle上的演示。

对于问题1,如果您在文本的最后一行附近单击某个位置,例如在“肩猪腰肉小腿tur腿肉。培根圆头沙朗火腿”中的某个地方,您会注意到偏移计算与IE(原始)不同解决方案)和IE方法2(我的解决方案)。另外,IE方法2(我的解决方案)和Chrome,Firefox的结果是相同的。

我的解决方案也简单得多。诀窍是,在使用TextRange在绝对X / Y位置进行选择后,通过调用document.getSelection()获得IHTMLSelection类型。这不适用于IE <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>";
  }  
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.