window.getSelection() en JavaScript - El suplicio de la selección de Texto
Índice de contenido
Te voy a explicar una cosita que creo que vas a considerar muy interesante que cuando el usuario selecciona un texto puedas establecer opciones como la de resaltar el texto y agregar una nota; para esto, existe una función en JS:
window.getSelection();Qué es window.getSelection() y cómo funciona
Si alguna vez has querido saber qué texto está seleccionando un usuario en tu página, window.getSelection() es tu mejor amigo. Esta función devuelve un objeto Selection que representa el rango de texto seleccionado o la posición actual del cursor (caret) dentro del documento.
Te devuelve un objeto con propiedades como:
- anchorNode y focusNode: nodos de inicio y fin de la selección.
- anchorOffset y focusOffset: posiciones exactas dentro de esos nodos.
- isCollapsed: si la selección está vacía.
- rangeCount: cuántos rangos contiene la selección.
Nota: document.getSelection() funciona prácticamente igual y, de hecho, en muchos casos puedes usarlo como sinónimo.
Obtener el texto seleccionado en JavaScript
Para probar el método anterior:
const selectedText = window.getSelection().toString();
console.log(selectedText);Abre una ventana de tu navegador, la consola de desarrolladores, selecciona un texto en la página web y luego ejecuta la línea de código anterior y verás ua saluda como la siguiente:
Selection {anchorNode: text, anchorOffset: 9, focusNode: text, focusOffset: 47, isCollapsed: false, …}
anchorNode
:
text
anchorOffset
:
9
baseNode
:
text
baseOffset
:
9
extentNode
:
text
extentOffset
:
47
focusNode
:
text
focusOffset
:
47
isCollapsed
:
false
rangeCount
:
1
type
:
"Range"Problemas con el window.getSelection();
El problema principal con el uso de window.getSelection(); es que si no tenemos un párrafo limpio como el siguiente:
<p id="p_1739646025565_337622">Laravel, al igua<span title="Test" id="_p_1739646025565_337622" class="bg-purple-700 rounded-sm no-set-note text-white">l que otros frame</span>works, puede ejecutarse en diversos entornos tanto en ambiente de producción como de desarrollo:</p>Si no algo como:
<p id="p_1739646025565_337622">Laravel, al igua<span title="Test" id="_p_1739646025565_337622" class="bg-purple-700 rounded-sm no-set-note text-white">l que otros frame</span>works, <span id="span_1739646025565_102820">puede ejecutarse en diversos entornos tanto en ambiente de producción como de desarrollo:</span></p>Y si por ejemplo, queremos seleccionar el texto de “iversos entornos tanto en ambien”, es decir, el texto justo al lado del SPAN, el rango definido será a partir del SPAN y no de todo el párrafo, por lo tanto, cualquier operación que queramos hacer, la posición estará vinculada al SPAN y no al párrafo lo cual es un gran problema ya que no es posible al menos de una manera sencilla, poder crear los rangos desde el párrafo.
Problemas comunes y limitaciones
En mi experiencia, hay varias trampas que no siempre aparecen en la documentación oficial:
- Rangos troceados por elementos internos
- Si tu párrafo tiene <span> internos, el rango a veces se genera desde el span y no desde el párrafo completo. Esto rompe cálculos de posición de burbujas y otras operaciones.
- Uso en inputs o textareas
- window.getSelection() no funciona bien en Firefox o Edge Legacy para contenido de <input> y <textarea>. La solución es usar setSelectionRange() o las propiedades selectionStart y selectionEnd.
- Reconstrucción de rangos guardados
- Para notas o highlights persistentes, recomiendo guardar el ID del nodo, start, end y un extracto del texto. Así puedes recrear el highlight más tarde sin problemas.
Recuperar el rango, resaltar texto y crear burbujas de nota
El rango, debemos de registrarlo si deseamos poder crear el mismo bajo demanda, para ello:
this.$axios
.post(this.$root.urls.bsnCreate + this.section.id, {
title: this.form.title,
content: this.form.content,
json: {
text: this.bubbleSelectecOptions.selectionText.substring(0, 10),
id: this.bubbleSelectecOptions.selectionId, // Guarda el ID del elemento padre
start: this.bubbleSelectecOptions.selectionRangeStart,
end: this.bubbleSelectecOptions.selectionRangeEnd
}En mi caso, lo uso para dibujar una burbuja con la opción de crear un comentario:
const rect = rango.getBoundingClientRect();
posX = rect.left + window.scrollX;
posY = rect.top + window.scrollY - 40;Luego, construyo un span dinámico:
const span = document.createElement("span");
span.title = "Mi nota";
span.className = "bg-purple-700 rounded-sm no-set-note text-white";Y lo mejor es que, si guardé el rango antes, puedo reconstruirlo más tarde:
const node = document.querySelector("#" + id).firstChild;
const rango = document.createRange();
rango.setStart(node, start);
rango.setEnd(node, end);Quedando el código completo:
async addHighlights(id, start, end, noteTitle, noteContent) {
// const node = document.querySelector("#" + id).firstChild.nodeValue
if (id.trim() == "")
return this.$toast.warning(this.$t('large.noteBookRangeNoValid'))
const node = document.querySelector("#" + id).firstChild
const rango = document.createRange();
try {
rango.setStart(node, start);
rango.setEnd(node, end);
const span = document.createElement("span");
span.title = noteTitle;
span.id = "_" + id;
span.onclick = function () {
this.bubbleSelectecOptions.titleNote = noteTitle
this.bubbleSelectecOptions.contentNote = noteContent
this.bubbleSelectecOptions.showModalNoteUser = true
this.bubbleSelectecOptions.modalKey3 = Date()
}.bind(this);
span.className = "bg-purple-700 rounded-sm no-set-note text-white";
rango.surroundContents(span);
} catch (error) {
console.error(error)
}
},Buenas prácticas y tips avanzados
- Maneja nodos parciales con cuidado: surroundContents() puede fallar si tu rango incluye nodos incompletos.
- Compatibilidad entre navegadores: testea en Chrome, Firefox, Edge y Safari.
- Optimización: evita recorrer todo el DOM cada vez que quieras detectar selección; trabaja con nodos padres específicos.
- Persistencia: siempre guarda el ID del nodo y offsets para poder reconstruir rangos sin errores.
Preguntas frecuentes (FAQ)
- 1. ¿Cuál es la diferencia entre window.getSelection() y document.getSelection()?
- Ambas devuelven el mismo tipo de objeto Selection, pero document.getSelection() siempre se refiere al documento actual, mientras que window.getSelection() puede usarse en contextos de ventana más amplios.
- 2. ¿Cómo obtener el texto exacto de un rango?
- Usa selection.toString() o range.toString() para obtener solo el texto, sin metadatos.
- 3. ¿Por qué falla cuando hay elementos internos como SPANs?
- El rango se genera a partir del nodo más interno, no del contenedor principal. La solución es reconstruirlo usando firstChild del nodo padre.
- 4. ¿Se puede usar en inputs o textareas?
- En la mayoría de navegadores sí, excepto Firefox y Edge Legacy, donde debes usar setSelectionRange().
- 5. ¿Cómo reconstruir rangos guardados para notas persistentes?
- Guarda id del nodo, start, end y un extracto del texto. Luego recrea el Range() y aplica surroundContents().
Acepto recibir anuncios de interes sobre este Blog.
Hablo sobre como estoy implementando una funcionalidad para la selección de texto en JavaScript y poder realizar un resaltado de texto y una nota.