Wie bekomme ich mit JavaScript ein Wort unter den Cursor?

77

Wenn ich zum Beispiel habe

<p> some long text </p>

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

Ivan
quelle
1
Dies ist eine Live-Demo, wie man mit JavaScript ein Wort unter den Cursor bekommt, basierend auf dem von Damovisa bereitgestellten Quellcode : jsfiddle.net/5gyRx .
Rubens Mariuzzo
2
@Ivan Es gibt eine neue Antwort auf diese Frage von einem Kopfgeld. Sie können es auswählen (für neue Benutzer, die kommen).
user1122069

Antworten:

43

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

Auf diese Weise müssen Sie nicht daran denken, Ihren Text mit einem Bereich um die Wörter auszugeben. Lassen Sie Ihr Javascript es 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, solange er funktioniert, alle HTML-Dateien in Ihren Absatz-Tags entfernt.

jsFiddle Beispiel

Damovisa
quelle
5
Oder Sie könnten einfach $(this).text().replace(/\b(\w+)\b/g, "<span>$1</span>")anstelle der Schleife tun . Dadurch werden alle Leerzeichen korrekt behandelt.
Chetan S
@Chetan - danke dafür, ich bin nicht sehr gut mit Regex, also habe ich es auf einfache Weise gemacht :) Ich habe es aktualisiert.
Damovisa
Ich habe darüber nachgedacht, aber es ist eine umständliche Lösung (ich bin ein Neuling in JavaScript, daher war mein Weg viel schlechter als der Ihre). Danke für die Klarstellung. @Chetan - das ist eine gute Lösung.
Ivan
Wie würden wir es so bearbeiten, dass es h1-, h2-, h3- usw. Tags anstelle von nur p-Tags identifiziert?
idude
@idude Sie sollten nur in der Lage sein, den ersten $('p')Selektor durch $('p,h1,h2,h3')und so weiter zu ersetzen . Um den Schwebeflug zum Laufen zu bringen, müssen Sie den zweiten Selektor auf ändern $('p span,h1 span,h2 span,h3 span').
Damovisa
40

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 an getWordAtPoint(e.target, e.x, e.y);

Eyal
quelle
Code funktioniert gut unter iOS (6/7), aber in Android 4.0.3 kann getBoundingClientRect null ergeben. Fügen Sie also hinzu: range.getBoundingClientRect ()! = Null als Bedingung in der ersten Schleife (bevor Sie die linke Eigenschaft erhalten).
Hugo Logmans
In den Dokumenten wird angegeben, dass die Grenze für "Wort" ein Leerzeichen ist. Aber die Erweiterung scheint für URLs nicht zu funktionieren. Irgendwelche Ideen?
Adam Gotterer
@Eyal Ich habe festgestellt, dass Ihr Code in Chrome und nicht in Firefox einwandfrei funktioniert. Wenn range.expand jedoch kommentiert wird, kann das Zeichen unter dem Cursor für Firefox angegeben werden. Haben Sie eine Idee, damit es in Firefox funktioniert?
Nagaraju
2
Dies ist ein schöner Code, der jedoch bei der Arbeit mit einer Mischung aus textNodes und anderen Inline-Elementen beschädigt wird. Es gibt zwei Fälle, in denen dies auftritt. 1. Ein Textknoten mit einem Zeilenumbruch hat einen unsinnigen Begrenzungsrahmen. 2. Inline-Elemente mit einer Höhe größer als die textNode-Zeile können die vertikale Position des Bereichs zurücksetzen. Ich denke, es sollte möglich sein, diese zu überwinden, indem textNodes von Anfang an zeichenweise überprüft und zufällige Zurücksetzungen der vertikalen Position kompensiert werden, indem angenommen wird, dass texNodes niemals höher sein können als eines ihrer vorherigen Geschwister (aber das ist möglicherweise nicht immer der Fall).
Jahu
Auch die +1 in der Bedingung in while-Schleife ist nicht erforderlich. Das letzte Zeichen des textNode beginnt bei range.endOffset(und endet bei range.endOffset + 1). Wenn die Bedingung nicht tatsächlich while(currentPos < endPos)das letzte Zeichen ist, wird es niemals getestet.
Jahu
39

Präambel:

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

Hier ist ein Beispiel aus der Kopfgeldfrage : Х</span>rт0съ. Wie komme ich richtig zurück Хrт0съ? Diese Probleme wurden bereits 2010 nicht angesprochen, daher werde ich jetzt (2015) zwei Lösungen vorstellen.


Lösung 1 - Entfernen Sie die inneren Tags und wickeln Sie die Felder um jedes vollständige Wort:

Eine Lösung besteht darin, die Span-Tags in Absätzen zu entfernen, aber ihren Text beizubehalten. Geteilte Wörter und Phrasen werden somit als normaler Text wieder zusammengefügt. Jedes Wort wird durch Leerzeicheneinteilung (nicht nur durch ein Leerzeichen) gefunden, und diese Wörter werden in Bereiche eingeschlossen, auf die einzeln zugegriffen werden kann.

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


Bild 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ремъ 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>

Lösung 1 Volltext-Demo


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

Hier ist eine anspruchsvollere Lösung. Es ist eine algorithmische Lösung mit Knotenüberquerung, die das vollständige und korrekte Wort unter einem Cursor in einem Textknoten genau erfasst.

Ein temporäres Wort wird gefunden, indem die Caret-Position überprüft wird (unter Verwendung von caretPositionFromPointoder caretRangeFromPoint, Credits für die Idee an @chrisv). Dies kann noch nicht das ganze Wort sein oder auch nicht.

Es wird dann analysiert, um festzustellen, ob es sich an einer Kante seines Textknotens befindet (Anfang oder Ende). Wenn dies der Fall ist, wird der vorherige Textknoten oder der folgende Textknoten untersucht, um festzustellen, ob er verbunden werden sollte, um dieses Wortfragment zu verlängern.

Beispiel:

Х</span>rт0съmuss zurückkehren Хrт0съ, nicht Хnoch rт0съ.

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

Beispiel:

њб.)</p><p>Во sollte nicht zurückkehren њб.)Во


In der Demo ist das linke schwebende Div das Wort unter dem Cursor. Das rechte schwebende Div zeigt, falls sichtbar, wie ein Wort an einer Grenze gebildet wurde. Andere Tags können sicher mit dem Text in dieser Lösung verknüpft werden.

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

( Hinweis: Ich habe mir erlaubt, Stile auf die Span-Tags anzuwenden, die in Ihrem Beispiel-HTML vorhanden waren, um zu beleuchten, wo sich Textknotenränder befinden.)

Lösung 2 Volltext-Demo

(Bisher in Chrome und IE. Für IE musste eine Methode von IERange als Shim für die browserübergreifende Kompatibilität verwendet werden.)

Drakes
quelle
In dieser slawischen Kodierung bedeutet das {einen Akzent, also würde ich nur ein Wort als alles innerhalb eines Leerzeichens zählen, sogar echte Interpunktionszeichen (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 auswählen.
user1122069
@ user1122069 Ich habe eine zweite Lösung veröffentlicht, eine viel bessere, die DOM-Traversal verwendet und auch im IE funktioniert. Es ist schnell und robust für zukünftiges HTML. Ich mag beide Lösungen, aber diese verwendet nicht das von Ihnen angeforderte Span-Tag-Wrapping.
Drakes
Vielen Dank. Funktioniert soweit perfekt. Ich habe die Funktionen als Objekt gekapselt, damit sie mit meiner Anwendung besser funktionieren. jsfiddle.net/ohaf4ytL/1 Ich denke, das wird auch für andere sehr nützlich sein.
user1122069
11

Meines Wissens können Sie nicht.

Ich kann mir nur vorstellen, jedes der Wörter in ein eigenes Element zu setzen und dann die Maus über Ereignisse 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>
Matt
quelle
2
Hier ist eine Demonstration des obigen Codes auf jsfiddle: jsfiddle.net/5bT4B
Anderson Green
9

Hier ist eine einfache Lösung, die in den meisten Fällen in Chrome 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;
}

Ich überlasse es dem Leser, Satzzeichen herauszufiltern und mit Bindestrichen versehene Wörter richtig zu behandeln :).

erwaman
quelle
1
Genau das, was ich für eine Chrome-Erweiterung brauchte.
Chemamoline
@chemamolins Genau das hat mich motiviert, dieses Rezept zu entwickeln :).
Erwaman
Die x / y-Koordinaten müssen event.clientX und nicht event.pageX sein. Bei Verwendung von pageX gibt caretRangeFromPoint () null zurück, wenn die Seite gescrollt wird und sich die Maus außerhalb der Koordinaten des ersten Ansichtsfensters befindet.
Tyshock
5

Dafür gibt es im aktuellen CSSOM View-Entwurf eine API :document.caretPositionFromPoint(x,y)

Sie müssten jedoch überprüfen, welcher Browser dies unterstützt. Firefox 7 scheint dies überhaupt nicht zu unterstützen, während Fehlerberichte darauf hinweisen, dass Firefox 9 dies tun wird. Chrome 14 unterstützt caretRangeFromPoint(x,y)im Wesentlichen dasselbe, jedoch aus einem älteren CSSOM-Entwurf.

chrisv
quelle
Es sieht so aus, als ob Ihre Antwort zu meiner Prämie für das Projekt passt. Es braucht nur ein wenig Arbeit, um das Wort zu finden, das vom Caret-Punkt aus erweitert wurde. Die native Methode zur Bereichserweiterung funktioniert nicht gut genug. Ich kann das selbst recherchieren, aber wenn Sie Code für die Arbeit mit meiner Demo jsfiddle.net/ohaf4ytL bereitstellen können, wäre das großartig.
user1122069
@ user1122069 Ich habe diese Lösung implementiert ( stackoverflow.com/a/30606508/2576706 ). Passt es zu Ihren Bedürfnissen?
Ludovic Feltz
5

Hier ist die Lösung für das Kopfgeld.

Wie von chrisv vorgeschlagen, können Sie document.caretRangeFromPoint( chrome ) oder document.caretPositionFromPoint(Firefox) verwenden. Ich denke, diese Lösung beantwortet Ihre Frage besser, da sie weder Ihren Text noch das DOM verändert.

Diese Funktion gibt das Wort unter dem Mauszeiger zurück, ohne das DOM zu ändern:

Aus der document.caretRangeFromPoint Dokumentation :

Die caretRangeFromPoint () -Methode der Document-Schnittstelle gibt ein Range-Objekt für das Dokumentfragment unter den angegebenen Koordinaten zurück.

Aus der document.caretPositionFromPoint Dokumentation :

Diese Methode wird verwendet, um die Caret-Position in einem Dokument basierend auf zwei Koordinaten abzurufen. Es wird eine CaretPosition zurückgegeben, die den gefundenen DOM-Knoten und den Zeichenversatz in diesem Knoten enthält.

Die beiden Funktionen unterscheiden sich geringfügig, geben jedoch den Knoten zurück, der den Text und den Versatz des Cursors in diesem Text enthält. So ist es einfach, das Wort unter die Maus zu bekommen.

Siehe das vollständige Beispiel:

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

Ludovic Feltz
quelle
"Holen Sie sich den HTML-Code in #hoverText - nur ein Wrapper für die Bequemlichkeit" - kommt mir bekannt vor
Drakes
@Drakes Ich habe die ganze Antwort hier gelesen und fand deine Idee, den Text in ein div-Gut zu verpacken, also habe ich den gleichen Namen behalten. Aber der Code danach ist völlig anders :)
Ludovic Feltz
@Drakes Übrigens fand ich deine Lösung sehr gut. Viel Glück für das Kopfgeld;)
Ludovic Feltz
"textNode ist undefiniert" aus irgendeinem Grund im IE.
Chan
Danke Ludovic. Genau das, was ich brauchte und es funktioniert wunderbar. Ich verwende es nur für Safari und habe festgestellt, dass caretRangeFromPoint den nächstgelegenen Bereich zurückgibt, selbst wenn Sie auf eine leere Stelle auf der Seite klicken. Scheint, als wäre es ein langjähriger Fehler: bugs.webkit.org/show_bug.cgi?id=29249
empedocle
3

Aw yiss! Hier ist ho!

So einfach es ist und ohne Jquery oder ein anderes Framework Fiddle: https://jsfiddle.net/703c96dr/

Jedes Wort wird überspannt und eine OnMouseover- und Onomouseout-Funktion hinzugefügt. Ich könnte eine einfache Klasse erstellen, um sie benutzerfreundlicher zu machen, aber der Code ist so einfach, dass jeder ihn bearbeiten und verwenden kann.

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

Einfacher Code

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;
    }
}
Guilherme Ferreira
quelle
2

Sie müssten den Absatz wahrscheinlich so aufteilen, dass jedes Wort in einem eigenen <span> -Element enthalten ist, und dann onmouseoverjedem von ihnen Ereignisattribute hinzufügen .

..Und ich denke du meinst "<p> etwas Langtext </ p>"; Backslashes sind nicht Teil von HTML.

Amphetamachine
quelle
2

In Firefox können Sie das Mousemove-Ereignis einbinden. Der Rückruf hat ein Argument, z. Gehen Sie im Rückruf folgendermaßen vor:

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

Jetzt hat str den gesamten Text, über dem die Maus war. e.rangeOffset ist die Position des Mauszeigers innerhalb dieser Zeichenfolge. In Ihrem Fall wäre str "Langtext" und e.rangeOffset 11, wenn Sie in "Text" über dem "e" stehen würden.

Dieser Code wird etwas verwirrt, wenn Sie sich am Rand befinden, z. B. wenn sich der Mauszeiger in derselben Zeile wie der Text befindet, jedoch nach dem Ende. Um dies zu beheben, müssen Sie überprüfen, ob Sie sich tatsächlich über dem Text befinden. Hier ist der Test:

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

Diese Technik funktioniert in Firefox. Funktioniert nicht in Chrome.

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

Meine Antwort leitet sich aus Drakes '"Lösung 2 - Caret Inspection und DOM Traversal" ab. Vielen Dank an Drakes für den Hinweis auf diese Lösung!

Es gibt jedoch zwei Probleme mit Drakes 'Lösung 2, wenn Sie am IE arbeiten. (1) Der berechnete Offset ist falsch und (2) zu viel Code.

Siehe meine Demonstration auf JSFiddle hier .

Wenn Sie bei Problem 1 auf die letzte Zeile des Textes klicken, z. B. irgendwo in "Schulter Schweinelende Schenkel Turducken Schaft Kuh. Speckkugelspitzen-Lendenschinken.", Können Sie feststellen, dass die Offset-Berechnung bei IE (Original) anders ist Lösung) und IE-Methode 2 (meine Lösung). Auch die Ergebnisse von IE-Methode 2 (meine Lösung) und von Chrome, Firefox, sind dieselben.

Meine Lösung ist auch viel einfacher. Der Trick besteht darin, nach Verwendung von TextRange zur Auswahl an der absoluten X / Y-Position einen Typ von IHTMLSelection durch Aufrufen von document.getSelection () abzurufen. Dies funktioniert nicht für IE <9, aber wenn das für Sie in Ordnung ist, ist diese Methode viel einfacher. Eine weitere Einschränkung ist, dass bei IE der Nebeneffekt der Methode (wie bei der ursprünglichen Methode) eine Änderung der Auswahl ist (dh der Verlust der ursprünglichen Auswahl des Benutzers).

  // 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>";
  }  
Bing Ren
quelle