81 Stimmen

Wie bekomme ich mit JavaScript ein Wort unter den Cursor?

Wenn ich zum Beispiel Folgendes habe

<p> some long text </p>

Wie kann ich auf meiner HTML-Seite wissen, dass sich der Mauszeiger zum Beispiel über dem Wort "Text" befindet?

1 Stimmen

Dies ist eine Live-Demo für wie man mit JavaScript ein Wort unter den Cursor bringt auf der Grundlage des Quellcodes, der von Damovisa : jsfiddle.net/5gyRx .

2 Stimmen

@Ivan Es gibt eine neue Antwort auf diese Frage von einem Bounty. Sie könnten in Erwägung ziehen, sie auszuwählen (im Interesse neuer Benutzer, die kommen).

45voto

Eyal Punkte 5478

Meine andere Antwort funktioniert nur in Firefox. Diese Antwort funktioniert in Chrome. (Könnte auch in Firefox funktionieren, ich weiß es nicht.)

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

Rufen Sie in Ihrem Mousemove-Handler getWordAtPoint(e.target, e.x, e.y);

0 Stimmen

Code funktioniert gut auf iOS (6/7), aber in Android 4.0.3 getBoundingClientRect kann null ergeben. Fügen Sie also hinzu: range.getBoundingClientRect() != null als Bedingung in der ersten Schleife (bevor Sie die linke Eigenschaft erhalten).

0 Stimmen

In der Dokumentation steht, dass die Grenze für "Wort" ein Leerzeichen ist. Aber die Erweiterung scheint bei URLs nicht zu funktionieren. Irgendwelche Ideen?

0 Stimmen

@Eyal Ich fand Ihren Code läuft gut in Chrome und nicht in firefox.but, wenn range.expand kommentiert wird, ist es in der Lage, das Zeichen unter Cursor für firefox.any Idee, um es in firefox arbeiten?

43voto

Damovisa Punkte 18793

Zusätzlich zu den beiden anderen Antworten können Sie möglicherweise Ihre Absätze mit jQuery (oder allgemein mit Javascript) in Bereiche aufteilen.

Auf diese Weise müssen Sie sich keine Gedanken darüber machen, wie Sie Ihren Text mit Leerzeichen um die Wörter herum ausgeben. Lassen Sie Ihr Javascript das für Sie tun.

z.B..

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

Beachten Sie, dass der obige Code zwar funktioniert, aber alle HTML-Inhalte innerhalb Ihrer Absatz-Tags entfernt.

jsFiddle-Beispiel

6 Stimmen

Oder Sie könnten einfach $(this).text().replace(/\b(\w+)\b/g, "<span>$1</span>") anstelle der Schleife. Dadurch werden alle Leerzeichen korrekt behandelt.

0 Stimmen

@Chetan - danke dafür, ich bin nicht sehr gut mit Regex, also habe ich es auf die einfache Art gemacht :) Ich habe es aktualisiert.

0 Stimmen

Ich dachte darüber nach, aber es ist umständlich Lösung (ich bin Neuling in JavaScript, so mein Weg war viel schlechter als Ihre). Vielen Dank für die Klärung. @Chetan - das ist saubere Lösung.

41voto

Drakes Punkte 21987

Präambel:

Wenn Sie mehrere Bereiche und verschachteltes HTML haben, die Wörter (oder sogar Zeichen in Wörtern) trennen, werden alle oben genannten Lösungen Probleme haben, das vollständige und richtige Wort zurückzugeben.

Hier ist ein Beispiel aus der Kopfgeldfrage: </span>r0 . Wie man richtig zurückgibt r0 ? Diese Fragen wurden im Jahr 2010 nicht behandelt, daher werde ich jetzt (2015) zwei Lösungen vorstellen.


Lösung 1 - Entfernen Sie die inneren Tags und fassen Sie jedes vollständige Wort in einem Bogen zusammen:

Eine Lösung besteht darin, die span-Tags innerhalb von Absätzen zu entfernen, aber ihren Text beizubehalten. Getrennte Wörter und Sätze werden so wieder als normaler Text zusammengefügt. Jedes Wort wird durch eine Leerraumtrennung gefunden (nicht nur durch ein Leerzeichen), und diese Wörter werden in Abschnitte eingeschlossen, auf die einzeln zugegriffen werden kann.

In der Demo können Sie das gesamte Wort markieren und erhalten so den Text des gesamten Wortes.


pic 0

Code:

$(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 te, 3 e: </span>r0 R: <span class="kinovar"> 
2. #   p0: </span>c2 E S D: <span class="kinovar"># 3A. a fj nhz.</span>
  </p>
</div>

Lösung 1 Volltext-Demo


Lösung 2 - Caret-Inspektion und DOM-Traversal:

Hier ist eine anspruchsvollere Lösung. Es handelt sich um eine algorithmische Lösung, bei der ein Node-Traversal verwendet wird, das das vollständige und richtige Wort unter einem Cursor in einem Textknoten genau erfasst.

Ein temporäres Wort wird durch Überprüfung der Caret-Position gefunden (mit caretPositionFromPoint ou caretRangeFromPoint , die Idee stammt von @chrisv). Dies kann, muss aber nicht das ganze Wort sein, noch nicht.

Es wird dann analysiert, ob es sich an einem der Ränder seines Textknotens befindet (Anfang oder Ende). Ist dies der Fall, wird der vorherige oder der folgende Textknoten daraufhin untersucht, ob er verbunden werden sollte, um das Wortfragment zu verlängern.

</span>r0 muss zurückkehren r0 , nicht noch r0 .

Der DOM-Baum wird durchlaufen, um den nächsten nicht-barrierefreien Textknoten zu finden. Wenn zwei Wortfragmente durch ein <p> oder ein anderes Barriere-Tag, dann sind sie nicht benachbart und somit nicht Teil desselben Wortes.

.)</p><p> sollte nicht zurückkehren .)


In der Demo ist das linke schwebende Div das Wort unter dem Cursor. Das rechte schwebende Div, sofern sichtbar, zeigt an, wie ein Wort an einer Begrenzung gebildet wurde. Andere Tags können bei dieser Lösung sicher mit dem Text inline'd werden.

pic 1

Code:

$(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 wod;
  }

  // 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 te, 3 e: </span>r0 R: <span class="kinovar"> 2. #   p0: </span>c2 E S D: <span class="kinovar"># 3A. a fj nhz.</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>ta 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 te, 3 e: </span>r0 R: <span class="kinovar"> 2. #   p0: </span>c2 E S D: <span class="kinovar"># 3A. a fj nhz.<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, aw,  7. a 7:</span></p></div>

( Note : Ich habe mir die Freiheit genommen, Stile auf die span-Tags anzuwenden, die in Ihrem HTML-Beispiel vorhanden waren, um zu verdeutlichen, wo die Grenzen der Textknoten liegen).

Lösung 2 Volltext-Demo

(Funktioniert bisher in Chrome und IE. Für IE, eine Methode aus IERange musste aus Gründen der Cross-Browser-Kompatibilität als Shim verwendet werden)

0 Stimmen

In dieser slawischen Kodierung steht das { für einen Akzent, also würde ich ein Wort einfach als alles innerhalb eines Leerzeichens zählen, auch echte Satzzeichen (da ich sie selbst entfernen werde). Die Antwort entspricht technisch nicht der Prämie, aber wenn sie das Problem am besten löst, werde ich sie auswählen.

0 Stimmen

@user1122069 Ich habe eine zweite Lösung veröffentlicht, eine viel bessere, die DOM-Traversal verwendet und auch im IE funktioniert. Sie ist schnell und soll für künftiges HTML robust sein. Ich mag beide Lösungen, aber diese verwendet kein span-Tag-Wrapping, wie von Ihnen gewünscht.

0 Stimmen

Danke! Funktioniert bis jetzt perfekt. Ich habe die Funktionen als Objekt gekapselt, damit es besser mit meiner Anwendung funktioniert. jsfiddle.net/ohaf4ytL/1 Ich denke, dies wird auch für andere sehr nützlich sein.

11voto

Matt Punkte 72534

Meines Wissens kann man das nicht.

Das Einzige, was mir einfällt, ist, jedes der Wörter in ein eigenes Element zu packen und dann Mouse-Over-Events auf diese Elemente anzuwenden.

<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 Stimmen

Hier ist eine Demonstration des obigen Codes auf jsfiddle: jsfiddle.net/5bT4B

10voto

erwaman Punkte 2991

Hier ist eine einfache Lösung, die in Chrome in den meisten Fällen funktioniert:

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

Das Herausfiltern von Satzzeichen und die richtige Handhabung von Wörtern mit Bindestrich überlasse ich dem Leser :).

1 Stimmen

Genau das, was ich für eine Chrome-Erweiterung brauchte.

0 Stimmen

@chemamolins Das ist genau das, was mich zu diesem Rezept motiviert hat :).

0 Stimmen

Die x/y-Koordinaten müssen event.clientX und nicht event.pageX sein. Wenn pageX verwendet wird, gibt caretRangeFromPoint() null zurück, wenn die Seite gescrollt wird und die Maus sich außerhalb der Koordinaten des ursprünglichen Ansichtsfensters befindet.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X