Selection и Range API в JavaScript
Узнайте, как работать с Selection и Range API в JavaScript для выделения, извлечения и вставки содержимого в DOM.
Когда пользователь выделяет текст на странице, перетаскивая курсор, браузер отслеживает выделенную область. JavaScript предоставляет доступ к этой информации — и позволяет создавать выделения программно — через два связанных DOM-интерфейса:
Range— это пара граничных точек (начало и конец) внутри документа. Он описывает какую именно часть документа вы имеете в виду, вплоть до конкретного смещения символа внутри текстового узла. Range может существовать исключительно в памяти, не будучи видимым для пользователя.Selection— это то, что пользователь в данный момент выделил. По сути, это обёртка вокруг одного или нескольких Range, привязанная к выделению на экране и каретке (курсору текста).
Эти API нужны при создании редакторов насыщенного текста, функции «выделить и прокомментировать», пользовательского поиска с заменой или любого механизма, который программно читает, перемещает или стилизует выделенный текст. Эта глава охватывает построение Range, чтение и изменение выделения пользователя, а также их совместное использование для выделения и вставки содержимого.
Это основывается на структуре DOM. Если типы узлов и смещения для вас в новинку, сначала прочитайте Свойства узлов: тип, тег и содержимое и Изменение документа.
Знакомство с интерфейсом Selection
Интерфейс Selection представляет текст, выделенный пользователем, или текущую позицию каретки, когда ничего не выделено. Доступ к нему осуществляется через глобальный метод window.getSelection() (часто просто getSelection()). Внутренне selection хранит ноль или более объектов Range; на практике большинство браузеров поддерживают только один Range, поэтому getRangeAt(0) — это стандартный способ его получить.
Самый быстрый способ увидеть, что выделено, — это toString(), который возвращает выделенный текст в виде обычной строки:
// After the user highlights something on the page:
const selectedText = window.getSelection().toString();
console.log(selectedText); // whatever the user highlightedПолезные свойства и методы Selection
rangeCount: Количество Range в выделении —0, если ничего не выделено. Всегда проверяйте это перед вызовомgetRangeAt.toString(): Возвращает выделенный текст в виде строки.getRangeAt(index): ВозвращаетRangeпо заданному индексу (используйте0для текущего выделения).addRange(range): ДобавляетRangeк выделению, подсвечивая его на экране.removeAllRanges(): Полностью снимает выделение.removeRange(range): Удаляет один конкретный Range. Большинство браузеров поддерживают только один Range, поэтомуremoveAllRanges()является практическим выбором.collapse(node, offset): Сворачивает выделение в единственную точку (пустую каретку) внутриnode.
Распространённый паттерн — прочитать текущее выделение, изменить его, затем записать новое выделение обратно: removeAllRanges() с последующим addRange().
Практический пример: выделение текста
Чтобы подсветить то, что выделил пользователь, нужно взять его Range, извлечь выделенные узлы из документа, обернуть их в стилизованный <span> и вставить этот span обратно на место содержимого.
extractContents()удаляет выделенное содержимое из DOM и возвращает его как фрагмент документа, оставляя Range пустым (свёрнутым) на этом месте — именно туда мы затем вставляем 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>Знакомство с интерфейсом Range
Range обозначает фрагмент документа двумя граничными точками — началом и концом — каждая из которых определяется узлом и смещением. Значение смещения зависит от типа узла:
- Внутри текстового узла смещение — это индекс символа (например, смещение
5означает положение между 5-м и 6-м символом). - Внутри узла-элемента смещение считает дочерние узлы (например, смещение
0— перед первым дочерним узлом).
Создайте пустой Range с помощью document.createRange(), затем задайте его границы. Узлы, на которые нужно указать, можно найти с помощью getElementById / querySelector.
Задание границ
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 paragraphДва сокращения покрывают наиболее распространённые случаи, избавляя от необходимости вычислять смещения:
selectNode(node)— Range охватывает узел и его окружающие теги.selectNodeContents(node)— Range охватывает только то, что находится внутри узла.
Полезные методы Range
setStart(node, offset)/setEnd(node, offset): Задают начальную и конечную границы.selectNode(node)/selectNodeContents(node): Устанавливают обе границы вокруг узла или его содержимого.toString(): Текст внутри Range.cloneContents(): Возвращает копию содержимого Range в виде фрагмента документа, не изменяя документ.extractContents(): Перемещает содержимое из документа во фрагмент и возвращает его.deleteContents(): Удаляет содержимое Range и ничего не возвращает.cloneRange(): Возвращает копию самого объекта Range (не его содержимого).insertNode(node): Вставляет узел в начало Range.surroundContents(node): Оборачивает содержимое Range внутриnode— удобно для подсветки, но выбрасывает исключение, если Range частично пересекает не-текстовый элемент.
Запомните разницу:
cloneContents()копирует,extractContents()перемещает и возвращает,deleteContents()удаляет и отбрасывает.
Практический пример: извлечение текста
Этот пример читает всё содержимое элемента с помощью Range и преобразует текст, не затрагивая исходный DOM:
<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>В приведённом выше примере скрипт заменяет слово «sample» на «example» в извлечённом тексте перед его отображением в диалоговом окне. Это базовая операция, но она демонстрирует, как можно начать работать с текстом после его извлечения.
Расширенные операции с текстом
Помимо базовых манипуляций с текстом, интерфейсы Selection и Range позволяют выполнять более сложные операции, например вставлять узлы непосредственно в документ.
Пример: вставка текста
В этом примере используется div с атрибутом contenteditable: пользователь кликает, чтобы установить каретку, а кнопка вставляет 'Hello World' именно в этом месте. Обратите внимание, как deleteContents() сначала удаляет всё выделенное, затем вставляется новый текстовый узел и снова выделяется, чтобы каретка оказалась после него.
<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>Итоги
Range— это две граничные точки (узел + смещение), описывающие фрагмент документа; создаётся с помощьюdocument.createRange()и позиционируется черезsetStart/setEndили сокращенияselectNode/selectNodeContents.Selection, получаемый черезwindow.getSelection(), оборачивает выделение пользователя на экране. Читается с помощьюtoString()иgetRangeAt(0); перезаписывается черезremoveAllRanges()+addRange().- Для работы с содержимым:
cloneContents()копирует,extractContents()перемещает,deleteContents()отбрасывает, аinsertNode/surroundContentsвставляют узлы обратно.
Вместе они позволяют точно выделять, извлекать и вставлять содержимое — это основа редакторов насыщенного текста и инструментов аннотирования.
Продолжайте изучение: Изменение документа · Свойства узлов: тип, тег и содержимое · Поиск: getElement* и querySelector