W3docs

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

Практика

Практика
Какие из следующих утверждений верны относительно интерфейсов Range и Selection в JavaScript?
Какие из следующих утверждений верны относительно интерфейсов Range и Selection в JavaScript?
Was this page helpful?