Detectar en qué palabra se ha hecho clic dentro de un texto

Resuelto Cystack asked hace 13 años • 16 respuestas

Estoy creando un script JS que en algún momento puede, en una página determinada, permitir al usuario hacer clic en cualquier palabra y almacenar esta palabra en una variable.

Tengo una solución que es bastante fea e implica el análisis de clases usando jQuery: primero analizo todo el html, divido todo en cada espacio " "y vuelvo a agregar todo lo incluido en a <span class="word">word</span>, y luego agrego un evento con jQ para detectar clics en dicha clase, y usando $(this).innerHTML obtengo la palabra en la que se hizo clic.

Esto es lento y feo en muchos sentidos y esperaba que alguien conociera otra forma de lograrlo.

PD: Podría considerar ejecutarlo como una extensión del navegador, así que si no parece posible con un simple JS, y si conoce una API del navegador que lo permita, ¡no dude en mencionarla!

Una posible solución sería lograr que el usuario resalte la palabra en lugar de hacer clic en ella, ¡pero realmente me encantaría poder lograr lo mismo con solo un clic!

Cystack avatar Sep 27 '11 08:09 Cystack
Aceptado

Aquí hay una solución que funcionará sin agregar toneladas de espacios al documento (funciona en Webkit, Mozilla e IE9+):

https://jsfiddle.net/Vap7C/15/

    $(".clickable").click(function(e){
         s = window.getSelection();
         var range = s.getRangeAt(0);
         var node = s.anchorNode;
         
         // Find starting point
         while(range.toString().indexOf(' ') != 0) {                 
            range.setStart(node,(range.startOffset -1));
         }
         range.setStart(node, range.startOffset +1);
         
         // Find ending point
         do{
           range.setEnd(node,range.endOffset + 1);

        }while(range.toString().indexOf(' ') == -1 && range.toString().trim() != '');
        
        // Alert result
        var str = range.toString().trim();
        alert(str);
       });
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p class="clickable">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris rutrum ante nunc. Proin sit amet sem purus. Aliquam malesuada egestas metus, vel ornare purus sollicitudin at. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer porta turpis ut mi pharetra rhoncus. Ut accumsan, leo quis hendrerit luctus, purus nunc suscipit libero, sit amet lacinia turpis neque gravida sapien. Nulla facilisis neque sit amet lacus ornare consectetur non ac massa. In purus quam, imperdiet eget tempor eu, consectetur eget turpis. Curabitur mauris neque, venenatis a sollicitudin consectetur, hendrerit in arcu.
</p>
Expandir fragmento

en IE8, tiene problemas debido a getSelection. Este enlace ( ¿Existe una solución para varios navegadores para getSelection()? ) puede ayudar con esos problemas. No lo he probado en Opera.

Utilicé https://jsfiddle.net/Vap7C/1/ de una pregunta similar como punto de partida. Usó la función Selection.modify :

s.modify('extend','forward','word');
s.modify('extend','backward','word');

Desafortunadamente no siempre entienden la palabra completa. Como solución alternativa, obtuve el Rango para la selección y agregué dos bucles para encontrar los límites de las palabras. El primero sigue añadiendo caracteres a la palabra hasta llegar a un espacio. el segundo bucle va hasta el final de la palabra hasta llegar a un espacio.

Esto también tomará cualquier puntuación al final de la palabra, así que asegúrese de recortarla si es necesario.

stevendaniels avatar Feb 16 '2012 03:02 stevendaniels

Hasta donde yo sé, agregar un spanpara cada palabra es la única forma de hacerlo.

Podrías considerar usar Lettering.js , que se encarga de la división por ti. Aunque esto realmente no afectará el rendimiento, a menos que su "código de división" sea ineficiente.

Luego, en lugar de vincular .click()a each span, sería más eficiente vincular un single .click()al contenedor de s spany verificar event.targeten cuál spanse ha hecho clic.

thirtydot avatar Sep 27 '2011 01:09 thirtydot

Aquí hay mejoras para la respuesta aceptada:

$(".clickable").click(function (e) {
    var selection = window.getSelection();
    if (!selection || selection.rangeCount < 1) return true;
    var range = selection.getRangeAt(0);
    var node = selection.anchorNode;
    var word_regexp = /^\w*$/;

    // Extend the range backward until it matches word beginning
    while ((range.startOffset > 0) && range.toString().match(word_regexp)) {
      range.setStart(node, (range.startOffset - 1));
    }
    // Restore the valid word match after overshooting
    if (!range.toString().match(word_regexp)) {
      range.setStart(node, range.startOffset + 1);
    }

    // Extend the range forward until it matches word ending
    while ((range.endOffset < node.length) && range.toString().match(word_regexp)) {
      range.setEnd(node, range.endOffset + 1);
    }
    // Restore the valid word match after overshooting
    if (!range.toString().match(word_regexp)) {
      range.setEnd(node, range.endOffset - 1);
    }

    var word = range.toString();
});​
link0ff avatar Dec 27 '2016 12:12 link0ff

Y otra versión de la respuesta de @stevendaniel:

$('.clickable').click(function(){
   var sel=window.getSelection();
   var str=sel.anchorNode.nodeValue,len=str.length, a=b=sel.anchorOffset;
   while(str[a]!=' '&&a--){}; if (str[a]==' ') a++; // start of word
   while(str[b]!=' '&&b++<len){};                   // end of word+1
   console.log(str.substring(a,b));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<p class="clickable">The objective can also be achieved by simply analysing the
string you get from <code>sel=window.getSelection()</code>. Two simple searches for
the next blank before and after the word, pointed to by the current position
(<code>sel.anchorOffset</code>) and the work is done:</p>

<p>This second paragraph is <em>not</em> clickable. I tested this on Chrome and Internet explorer (IE11)</p>
Expandir fragmento

Carsten Massmann avatar Jul 31 '2018 14:07 Carsten Massmann