web-dev-qa-db-fra.com

Comment obtenir un mot sous le curseur en utilisant JavaScript?

Si par exemple j'ai 

<p> some long text </p>

sur ma page HTML, comment puis-je savoir que le curseur de la souris est par exemple au-dessus du mot 'texte'?

68
Ivan

Outre les deux autres réponses, vous pourrez peut-être diviser vos paragraphes en plusieurs parties à l’aide de jQuery (ou javascript en général).

De cette façon, vous n’auriez pas besoin de penser à la sortie de votre texte avec une étendue autour des mots. Laissez votre javascript le faire pour vous.

par exemple.

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

Notez que le code ci-dessus, tout en fonctionnant, supprimera tout code HTML contenu dans vos balises de paragraphe.

Exemple jsFiddle

40
Damovisa

Mon autre réponse ne fonctionne que dans Firefox. Cette réponse fonctionne dans Chrome. (Peut aussi fonctionner dans Firefox, je ne sais pas.)

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

Dans votre gestionnaire de souris, appelez getWordAtPoint(e.target, e.x, e.y);

38
Eyal

À ma connaissance, vous ne pouvez pas.

La seule chose à laquelle je peux penser est de mettre chacun des mots dans leur propre élément, puis d'appliquer la souris sur les événements à ces éléments.

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

Il existe une API pour cela dans le projet en cours CSSOM View : document.caretPositionFromPoint(x,y)

Cependant, vous devrez vérifier quel navigateur le supporte. Firefox 7 semble ne pas le supporter du tout, alors que les rapports de bogues indiquent que Firefox 9 le fera. Chrome 14 prend en charge caretRangeFromPoint(x,y), qui est essentiellement identique, mais à partir d'un brouillon CSSOM plus ancien. 

5
chrisv

Voici la solution pour la prime.

Comme suggéré par chrisv vous pouvez utiliser document.caretRangeFromPoint (chrome) ou document.caretPositionFromPoint (Firefox) . Je pense que cette solution répondra mieux à votre question car elle ne modifie ni votre texte ni le DOM.

Cette fonction retourne le mot sous le curseur de la souris sans modifier le DOM:

De la document.caretRangeFromPointdocumentation :

La méthode caretRangeFromPoint () de l'interface Document renvoie un objet Range pour le fragment de document sous les coordonnées spécifiées.

De la document.caretPositionFromPointdocumentation :

Cette méthode est utilisée pour récupérer la position du curseur dans un document en fonction de deux coordonnées. Une CaretPosition est retournée, contenant le nœud DOM trouvé et le décalage de caractère dans ce nœud.

Les deux fonctions sont légèrement différentes mais elles renvoient toutes les deux le nœud contenant le texte et le décalage du curseur dans ce texte. Il est donc facile d’obtenir le mot sous la souris.

Voir l'exemple complet:

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

5
Ludovic Feltz

Voici une solution simple qui fonctionne dans Chrome dans la plupart des cas:

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

Je laisse filtrer la ponctuation et gérer correctement les mots composés comme un exercice pour le lecteur :).

4
erwaman

Aw yiss! Voici ho!

Aussi simple que cela et sans Jquery ou tout autre framework Fiddle: https://jsfiddle.net/703c96dr/

Il mettra des étendues sur chaque mot et ajoutera une fonction onmouseover et onomouseout. Je pourrais créer une classe simple pour la rendre plus utilisable, mais le code est si simple que tout le monde peut l'éditer et l'utiliser.

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

Code simple

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

Vous devrez probablement diviser le paragraphe afin que chaque mot soit contenu dans son propre élément <span> distinct, puis ajouter des attributs d'événement onmouseover à chacun d'eux.

Et je pense que vous voulez dire "<p> un long texte </ p>"; les barres obliques inverses ne font pas partie du code HTML.

2
amphetamachine

Dans Firefox, vous pouvez accrocher l'événement mousemove. Le rappel a un argument, e. Dans le rappel, procédez comme suit:

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

Maintenant str a l'intégralité du texte sur lequel la souris était passée e.rangeOffset est l'emplacement du pointeur de souris dans cette chaîne. Dans votre cas, str serait "un long texte" et e.rangeOffset serait 11 si vous aviez dépassé le "e" dans "texte".

Ce code sera un peu confus si vous vous trouvez dans les marges, par exemple lorsque le pointeur de la souris se trouve sur la même ligne que le texte mais après la fin de celui-ci. Pour résoudre ce problème, vous devez vérifier que vous êtes réellement au-dessus du texte. Voici le test:

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

Cette technique fonctionne dans Firefox. Ne fonctionne pas dans Chrome.

1
Eyal

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>

Ma réponse est tirée de la "Solution 2 - Inspection de caret et traversée de DOM" de Drakes. Merci beaucoup à Drakes pour avoir signalé cette solution!

Cependant, la solution 2 de Drakes pose un problème lorsque vous travaillez avec IE. (1) le décalage calculé est incorrect, et (2) trop complexe, beaucoup de code. 

Voir ma démonstration sur JSFiddle à ici .

Pour le problème 1, si vous cliquez quelque part à peu près sur la dernière ligne du texte, par exemple, dans "Vache à queue de longe de porc, queue de boudin turducken. Cuisse de jambon à pointe de boule au bacon", vous remarquerez que le calcul du décalage est différent avec IE (solution d'origine) et IE méthode 2 (ma solution). De plus, les résultats de la méthode IE 2 (ma solution) et de Chrome, Firefox sont les mêmes. 

Ma solution est aussi beaucoup plus simple. L'astuce consiste, après utilisation de TextRange à effectuer une sélection à la position absolue X/Y, à obtenir un type IHTMLSelection en appelant document.getSelection (). Cela ne fonctionne pas pour IE <9 mais si cela vous convient, cette méthode est beaucoup plus simple. Un autre inconvénient est que, avec IE, l'effet secondaire de la méthode (identique à la méthode d'origine) est le changement de sélection (c'est-à-dire la perte de la sélection d'origine de l'utilisateur). 

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