Detectar en qué palabra se ha hecho clic dentro de un texto
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!
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>
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.
Hasta donde yo sé, agregar un span
para 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 span
y verificar event.target
en cuál span
se ha hecho clic.
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();
});
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>