API Selection e Range in JavaScript
Scopri come usare le API Selection e Range di JavaScript per leggere, modificare e manipolare il testo selezionato nel DOM.
Quando un utente trascina il cursore sul testo di una pagina, il browser tiene traccia di ciò che è stato selezionato. JavaScript espone queste informazioni — e consente di creare selezioni a livello di codice — attraverso due interfacce DOM correlate:
- Un
Rangeè una coppia di punti limite (un inizio e una fine) all'interno del documento. Descrive quale parte del documento si intende, fino a uno specifico offset di carattere all'interno di un nodo di testo. Un range può esistere puramente in memoria senza che l'utente ne sia consapevole. - Una
Selectionè ciò che l'utente ha attualmente evidenziato. È essenzialmente un wrapper attorno a uno o più range, legato all'evidenziazione sullo schermo e al caret (cursore di testo).
Si ricorre a queste API quando si vuole creare un editor di testo ricco, una funzionalità di "evidenzia e commenta", una ricerca e sostituzione personalizzata, o qualsiasi cosa che legga, sposti o applichi stili al testo selezionato a livello di codice. Questo capitolo tratta la costruzione dei range, la lettura e la modifica della selezione dell'utente, e come combinarle per evidenziare e inserire contenuto.
Questo argomento si basa sulla struttura del DOM. Se i tipi di nodo e gli offset sono nuovi per te, leggi prima Proprietà del nodo: tipo, tag e contenuto e Modifica del documento.
Comprendere l'interfaccia Selection
L'interfaccia Selection rappresenta il testo evidenziato dall'utente, oppure la posizione corrente del caret quando non è evidenziato nulla. Vi si accede tramite il metodo globale window.getSelection() (spesso scritto semplicemente come getSelection()). Internamente una selezione contiene zero o più oggetti Range; in pratica la maggior parte dei browser mantiene un solo range, quindi getRangeAt(0) è il modo comune per leggerlo.
Il modo più rapido per vedere cosa è selezionato è toString(), che restituisce il testo selezionato come stringa semplice:
// After the user highlights something on the page:
const selectedText = window.getSelection().toString();
console.log(selectedText); // whatever the user highlightedProprietà e metodi utili di Selection
rangeCount: Il numero di range nella selezione —0quando non è selezionato nulla. Verificare sempre questo valore prima di chiamaregetRangeAt.toString(): Restituisce il testo selezionato come stringa.getRangeAt(index): Restituisce ilRangeall'indice specificato (usare0per la selezione corrente).addRange(range): Aggiunge unRangealla selezione, evidenziandolo sullo schermo.removeAllRanges(): Cancella completamente la selezione.removeRange(range): Rimuove un range specifico. La maggior parte dei browser mantiene un solo range, quindiremoveAllRanges()è la scelta pratica.collapse(node, offset): Riduce la selezione a un singolo punto (un caret vuoto) all'interno dinode.
Uno schema comune è leggere la selezione corrente, modificarla, quindi riscrivere una nuova selezione — removeAllRanges() seguito da addRange().
Esempio pratico: evidenziare il testo
Per evidenziare ciò che l'utente ha selezionato, si prende il suo range, si estraggono i nodi selezionati dal documento, li si inserisce in un <span> con stile e si rimette tale span dove si trovava il contenuto.
extractContents()rimuove il contenuto selezionato dal DOM e lo restituisce come frammento di documento, lasciando il range vuoto (collapsed) in quel punto — che è esattamente dove inseriamo poi lo span.
<div id="text">Select some of this text and press the button.</div>
<button onclick="highlightText()">Highlight</button>
<script>
function highlightText() {
const selection = window.getSelection();
if (!selection.rangeCount) return false;
const range = selection.getRangeAt(0);
const span = document.createElement('span');
span.style.backgroundColor = 'yellow';
const fragment = range.extractContents();
span.appendChild(fragment);
range.insertNode(span);
}
</script>Esplorare l'interfaccia Range
Un Range delimita un frammento del documento con due punti limite — un inizio e una fine — ciascuno definito da un nodo e da un offset. Il significato dell'offset dipende dal nodo:
- All'interno di un nodo di testo, l'offset è un indice di carattere (es. offset
5è tra il 5° e il 6° carattere). - All'interno di un nodo elemento, l'offset conta i nodi figlio (es. offset
0è prima del primo figlio).
Si crea un range vuoto con document.createRange(), poi si posizionano i suoi limiti. È possibile trovare i nodi a cui puntare con getElementById / querySelector.
Impostare i limiti
const p = document.querySelector('p');
const textNode = p.firstChild; // the text inside <p>
const range = document.createRange();
range.setStart(textNode, 0); // start at the first character
range.setEnd(textNode, 5); // end before the 6th character
console.log(range.toString()); // first 5 characters of the paragraphDue scorciatoie coprono i casi più comuni così da non dover calcolare gli offset:
selectNode(node)— il range comprende il nodo e i suoi tag circostanti.selectNodeContents(node)— il range comprende solo ciò che si trova all'interno del nodo.
Metodi utili di Range
setStart(node, offset)/setEnd(node, offset): Posizionano i limiti di inizio e fine.selectNode(node)/selectNodeContents(node): Impostano entrambi i limiti attorno a un nodo o al suo contenuto.toString(): Il testo all'interno del range.cloneContents(): Restituisce una copia del contenuto del range come frammento di documento, senza toccare il documento.extractContents(): Sposta il contenuto fuori dal documento in un frammento e lo restituisce.deleteContents(): Rimuove il contenuto del range senza restituire nulla.cloneRange(): Restituisce una copia dell'oggetto range stesso (non del suo contenuto).insertNode(node): Inserisce un nodo all'inizio del range.surroundContents(node): Avvolge il contenuto del range all'interno dinode— utile per l'evidenziazione, ma genera un errore se il range attraversa parzialmente un elemento non testuale.
Ricordare la differenza:
cloneContents()copia,extractContents()sposta fuori e restituisce,deleteContents()rimuove e scarta.
Esempio pratico: estrarre testo
Questo codice legge tutto il contenuto all'interno di un elemento con un range e trasforma il testo senza toccare il DOM originale:
<div id="content">This is some sample text for extraction.</div>
<button onclick="extractText()">Extract and Manipulate</button>
<script>
function extractText() {
const range = document.createRange();
const content = document.getElementById('content');
range.selectNodeContents(content);
const extractedText = range.toString();
const manipulatedText = extractedText.replace('sample', 'example'); // Manipulating text
alert(manipulatedText);
}
</script>Nell'esempio precedente, lo script sostituisce la parola "sample" con "example" nel testo estratto prima di mostrarlo in una finestra di avviso. Si tratta di una manipolazione di base, ma dimostra come si possa iniziare a lavorare con il testo una volta estratto.
Operazioni avanzate sul testo
Oltre alla manipolazione di base del testo, le interfacce Selection e Range consentono operazioni più complesse, come l'inserimento di nodi direttamente nel documento.
Esempio: inserimento di testo
Questo esempio utilizza un div con contenteditable: l'utente fa clic per posizionare il caret e il pulsante inserisce 'Hello World' esattamente in quel punto. Si noti come deleteContents() rimuova prima il testo selezionato, poi il nuovo nodo di testo viene inserito e ri-selezionato in modo che il caret si posizioni dopo di esso.
<div id="editable" contenteditable="true" style="border: 1px solid #ccc; padding: 10px; min-height: 50px;">
Click here and set the cursor position.
</div>
<button onclick="insertText()">Insert 'Hello World'</button>
<script>
function insertText() {
const editableDiv = document.getElementById('editable');
const sel = window.getSelection();
// Check if the selection is within the editable div
if (!sel.rangeCount || !editableDiv.contains(sel.getRangeAt(0).commonAncestorContainer)) return;
const range = sel.getRangeAt(0);
range.deleteContents(); // Clears any selected text
const textNode = document.createTextNode('Hello World');
range.insertNode(textNode);
sel.removeAllRanges(); // Clear the previous selection
sel.addRange(range); // Re-select the new text node
}
</script>Riepilogo
- Un
Rangeè composto da due punti limite (nodo + offset) che descrivono un frammento del documento; si crea condocument.createRange()e si posiziona consetStart/setEndoppure con le scorciatoieselectNode/selectNodeContents. - Una
Selection, ottenuta dawindow.getSelection(), racchiude l'evidenziazione dell'utente sullo schermo. Si legge contoString()egetRangeAt(0); si riscrive conremoveAllRanges()+addRange(). - Per il contenuto:
cloneContents()copia,extractContents()sposta fuori,deleteContents()scarta, mentreinsertNode/surroundContentsreinseriscono i nodi.
Insieme, questi strumenti consentono di evidenziare, estrarre e inserire contenuto con precisione — la base degli editor di testo ricchi e degli strumenti di annotazione.
Continua: Modifica del documento · Proprietà del nodo: tipo, tag e contenuto · Ricerca: getElement* e querySelector