Будьте в курсе последних событий, подпишитесь на обновления сайта

Выделение на странице: Range, TextRange и Selection

Выделение на странице: Range, TextRange и Selection

Здравствуйте! В этой статье я  расскажу  об  объектах Range, TextRange и Selection. Эти объекты используются для выделения текста и  задания стилевых свойств выделяемому фрагменту. В принципе данные объекты  используются не часто, но знать о них все равно надо.

Выделение текста Объект Range, Text Range

Range

Range – это объект, соответствующий фрагменту веб-страницы, который может включать узлы и участки текста из этого документа.

Чтобы понять о чем речь, обратимся к самому простому случаю Range, который будет подробно рассмотрен ниже – к выделениям текста.

Но такие области можно создавать не только с помощью пользовательского выделения, но и из JavaScript-сценария, выполняя с ними определенные действия. Однако, написать простой иллюстрирующий код сразу не выйдет, т.к. есть одно НО – Internet Explorer до версии 9. Дело в  том, что в Microsoft создали собственную реализацию – объект TextRange. Разберём каждую реализацию по-отдельности.

DOM-реализация Range (кроме IE8-)

Range состоит из двух граничных точек (boundary-points), соответствующих началу и концу  выделяемой области. Позиция любой граничной точки определяется в документе с помощью двух свойств: узел (node) и смещение (offset).

Контейнером (container) называют узел, который содержит граничную точку. Сам контейнер и все его предки называются родительскими контейнерами (ancestor containers) для граничной точки. Родительский контейнер, включающий все граничные точки, называют корневым контейнером (root container).
range javascript

На рисунке выше граничные точки выделения лежат в текстовых узлах (#text1 и #text2), которые и будут контейнерами.

Для левой границы родительскими контейнерами являются #text1, H1, BODY, для правой – #text2, P, BODY. Общий родитель для обоих граничных точек –  это BODY, этот элемент является корневым контейнером.

Если контейнер  — это текстовый узел, то смещение определяется в символах от начала DOM-узла. Если контейнер является элементом (Document, DocumentFragment, Element…), то смещение определяется в дочерних узлах.

Text Range Javascript

Граничные точки объекта Range s1 лежат в текстовых узлах, поэтому смещение задается в символах от начала узла. Для s2 граничные точки расставлены так, что включают весь абзац <p>Blah xyz</p>, поэтому контейнером является элемент BODY, и смещение считается в позициях дочерних узлов.

Объекты Range создаются с помощью вызова  метода document.createRange(). Объект при этом создается пустой, и граничные точки нужно задать далее него соотвественно методами setStart и setEnd. Вот например.

HTML:

<div id="ex2"> 
<h2>Соз|даем объект Range</h2> 
<p>От третье|го символа заголовка до десятого символа это абзаца.</p> 
</div> 
<button onclick="alert(domRangeCreate())"> Создать Range и вывести его текст </button> 
<script>
  function domRangeCreate() { 
// Найдем корневой контейнер 
var root = document.getElementById('ex2'); 
// Найдем контейнеры граничных точек (в данном случае тестовые) 
var start = root.getElementsByTagName('h2')[0].firstChild; 
var end = root.getElementsByTagName('p')[0].firstChild; 
if (root.createRange) { 
// Создаем Range 
var rng = root.createRange(); 
// Задаем верхнюю граничную точку, передав контейнер и смещение 
rng.setStart(start, 3); 
// Аналогично для нижней границы 
rng.setEnd(end, 10); 
// Теперь мы можем вернуть текст, который содержится в полученной области 
return rng.toString(); } 
else { return 'Вероятно, у вас IE8-, смотрите реализацию TextRange ниже'; } } 
</script>

Рассмотрим вкратце свойства и методы Range:

  • Свойство commonAncestorContainer вернет ссылку на наиболее вложенный корневой контейнер.
  • Свойство startContainer (endContainer) вернет ссылку на контейнер верхней (нижней) точки граничной.
  • Свойство startOffset (endOffset) вернет смещение для верхней (нижней) точки граничной.
  • Свойство collapsed вернет true, если граничные точки имеют одинаковые контейнеры и смещение (false в противном случае).
  • Метод setStart (setEnd) задает контейнер (ссылка на узел) и смещение (целочисленное значение) для соответствующих точек граничных.  Смотри пример выше.
  • Методы setStartBefore, setStartAfter, setEndBefore, setEndAfter принимают в качестве единственного аргумента ссылку на узел и устанавливают граничные точки в соотвествии с естественной границей переданного узла. Например:
    <span id="s1">First</span> <span id="s2">Second</span>
    
var rng = document.createRange(); 
//Установит верхнюю граничную точку по левой границе спана #s1 
rng.setStartBefore(document.getElementById('s1')); 
//Установит нижнюю граничную точку по правой границе спана #s2 
rng.setEndAfter(document.getElementById('s2'));
  • Методы selectNode и selectNodeContents могут создать объект Range по границам узла, ссылку на который они примут в качестве аргумента. При использовании selectNode узел также войдет в Range, в то время как selectNodeContents создаст объект только из содержимого узла:
    selectnode javascript

     

  • Метод collapse объединяет граничные точки объекта Range. В качестве единственного аргумента принимает булево значение (true – для объединения в верхней точке, false – в нижней). По умолчанию используется true.
  • Метод toString возвращает текстовое содержимое объекта Range.
  • Метод cloneContents возвращает копию содержимого объекта Range в виде фрагмента документа.
  • Метод cloneRange возвращает копию самого объекта Range.
  • Метод deleteContents удаляет всё содержимое из объекта Range.
  • Метод detach извлекает объект из DOM, так что на него больше нельзя сослаться.
  • Метод insertNode принимает в качестве единственного аргумента ссылку на узел и вставляет его в содержимое объекта Range в начальной точке.
  • Метод extractContents вырезает содержимое всего объекта Range и возвращает ссылку на полученный фрагмент документа.
  • Метод surroundContents помещает всё содержимое текущего объекта Range в новый родительский элемент, ссылка на который принимается в качестве единственного аргумента.
  • Метод compareBoundaryPoints используется для сравнения граничных точек.
Читайте также  Как создать переменную в JavaScript

Для примера решим небольшую задачку. Найдём в текстовом узле фразу и подсветим её синим фоном.

<div id="ex3"> Найдем в этом тексте слово "бабуля" и подсветим его красным фоном </div> 
<script>
  function domRangeHighlight(text) { 
// Получим текстовый узел 
var root = document.getElementById('ex3').firstChild; 
// и его содержимое 
var content = root.nodeValue; 
// Проверим есть ли совпадения с переданным текстом 
if (~content.indexOf(text)) { 
if (document.createRange) { 
// Если есть совпадение, и браузер поддерживает Range, создаем объект 
var rng = document.createRange(); 
// Ставим верхнюю границу по индексу совпадения, 
rng.setStart(root, content.indexOf(text)); 
// а нижнюю по индексу + длина текста 
rng.setEnd(root, content.indexOf(text) + text.length); 
// Создаем спан с синим фоном 
var highlightDiv = document.createElement('span'); 
highlightDiv.style.backgroundColor = 'red'; 
// Обернем наш Range в спан 
rng.surroundContents(highlightDiv); } 
else { alert( 'Вероятно, у вас IE8-, смотрите реализацию TextRange ниже' ); } } 
else { alert( 'Совпадений не найдено' ); } } </script>

С остальными свойствами и методами  вы можете поэкспериментировать сами.

TextRange (для IE)

Объект TextRange в реализации MSIE – это просто текстовый диапазон нулевой и большей длины. У данного диапазона также есть свои границы, «перемещать» которые можно на определенное число текстовых единиц: character(символ), word (слово), sentence (предложение).

То есть можно взять и сдвинуть границу на 2(5, 8 и т.д.) слова (символа, предложения) вправо (влево). При этом у объекта сохраняются данные о HTML-содержимом диапазона и есть методы взаимодействия с DOM.

Объект TextRange создается с помощью метода createTextRange, который можно вызывать в контексте элементов BODY, BUTTON, INPUT (большинство типов), TEXTAREA.

Вот простой пример с кнопкой:

<input id="buttonId" type="button" 
value="Test button" onclick="alert( ieTextRangeCreate() );" /> 
<script>
  function ieTextRangeCreate() { 
// Найдем кнопку 
var button = document.getElementById('buttonId'); 
// Если мы в ИЕ 
if (button.createTextRange && button.createTextRange() != undefined) { 
// Создаем TextRange 
var rng = button.createTextRange(); 
// И вернем текстовое содержимое полученного объекта 
return rng.text; } 
else { return 'Вероятно, у вас не IE, смотрите реализацию Range выше'; } } 
</script>

Рассмотрим свойства и методы объекта TextRange:

  • Свойство boundingWidth (boundingHeight) возвращает ширину (высоту), которую занимает объект TextRange в пикселях.
  • Свойство boundingTop (boundingLeft) возвращает Y(X)-координату верхнего левого угла тестовой области относительно окна документа.
  • Свойство htmlText возвращает HTML-содержимое объекта.
  • Свойство text возвращает текстовое содержимое объекта.
  • Свойство offsetTop (offsetLeft) возвращает Y(X)-координату верхнего левого угла тестовой области относительно его предка.
  • Метод collapse объединяет граничные точки диапазона. В качестве аргумента принимает логическое значение (true – для объединения в верхней точке, false – в нижней). По умолчанию используется true.
  • Метод duplicate копирует имеющийся текстовый диапазон, возвращая новый, точно такой же.
  • Метод expand расширит тектовый диапазон до единицы текста, переданной в качестве единственного текстового аргумента:
    • «character’ – символ.
    • «word» – слово
    • «sentence» – предложение
    • «textedit» – сворачивает до первоначального диапазона.

    Вернет true (false) в случае успеха (неудачи).

  • Метод findText ищет в диапазоне совпадения с текстовой строкой, передаваемой в качестве первого аргумента (регистр не учитывается). Если совпадение найдено, то границы диапазона сворачиваются до него. В качестве второго (необязательного) аргумента можно передать целое число, указывающее число символов от верхней точки, в которых нужно производить поиск. Далее в качестве аргументов можно перечислять INT-флаги, которые вам вряд ли понадобятся.
  • Метод getBookmark возвращает в случае успешного вызова строку, по которой можно будет восстановить текущее состояние текстового диапазона с помощью метода moveToBookmark.
  • Метод inRange принимает в качестве аргумента другой  диапозон TextRange и проверяет, входит ли его текстовый диапазон в диапазон контекстного объекта. Возвращает логическое значение.
  • Метод isEqual проверяет является ли текущий диапозон TextRange идентичным переданному в качестве аргумента. Возвращает логическое значение.
  • Метод move(sUnit [, iCount]) сворачивает данный диапазон до нулевой длины и передвигает на единицу текста, переданного в качестве первого аргумента (character | word | sentence | textedit). В качестве второго (необязательного) аргумента можно передать число единиц, на которое следует передвинуть диапазон.
  • Метод moveEnd (moveStart), аналогично методу move, передвигает верхнюю (нижнюю) границу диапазона на единицу текста, число которых также можно задать необязательным вторым параметром.
  • Метод moveToElementText принимает в качестве аргумента ссылку на DOM-элемент и выставляет границы диапазона Textобъекта Range по границам полученного элемента.
  • Метод moveToPoint принимает в качестве двух обязательных аргументов X и Y-координаты (в пикселях) относительно верхнего левого угла документа и переносит границы диапазона туда.
  • Метод parentElement вернет ссылку на элемент, который полностью содержит диапазон объекта TextRange (или null).
  • Метод pasteHTML заменяет HTML-содержимое текущего текстового диапазона на строку, переданную в качестве единственного аргумента.
  • Метод select формирует выделение на основе содержимого текстового объекта TextRange.
  • Метод setEndPoint принимает в качестве обязательных аргументов текстовый указатель и ссылку на другой диапозон TextRange, устанавливая в зависимости от значения указателя границы диапазона. Указатели могут быть следующими: „StartToEnd“, „StartToStart“, „EndToStart“, „EndToEnd“.

Также к TextRange применимы команды метода execCommand, который умеет делать текст жирным, курсивным, копировать его в буфер обмена.

Для закрепления давайте  сделаем задачку по поиску текстового содержимого, аналогичную той, что была выше:

<div id="ex4"> Найдем в этом тексте слово "бабуля" и подсветим его синим фоном </div> 
<script>
  function ieTextRangeHighlight(text) { 
// Получим ссылку на элемент, в котором будет происходить поиск 
var root = document.getElementById('ex4'); 
// Получим значение его текстового потомка 
var content = root.firstChild.nodeValue; 
// Если есть совпадение 
if (~content.indexOf(text)) { 
// и мы в MSIE 
if (document.body.createTextRange) { 
// Создадим объект TextRange 
var rng = document.body.createTextRange(); 
// Свернем его до root 
rng.moveToElementText(root); 
// Найдем текст и свернем диапазон до него 
if (rng.findText(text)) //Заменим текстовый фрагмент на span с синим фоном 
rng.pasteHTML('<span style="background:blue;">' + text + '</span>'); } 
else { alert( 'Вероятно, у вас не  IE, смотрите реализацию Range выше' ); } } 
else { alert( 'Совпадений не найдено' ); } } 
</script>

Selection

Всем вам знакомо выделение элементов на странице, когда, вы зажав левую кнопку мыши и передвигая курсор, выделяете нужный фрагмент.

Или зажимаем клавишу Shift и жмём на стрелочки клавиатуры. Или еще каким-то способом, неважно. Тут мы  рассмотрим как можно выделять фрагменты  на веб-странице програмно.

Получаем пользовательское выделение

Эту задачу мы уже решали в самом начале статьи в примере с миксом. Теперь давайте рассмотрим код:

function getSelectionText() { 
var txt = ''; if (txt = window.getSelection) { 
// Не IE, используем метод 
getSelection txt = window.getSelection().toString(); } 
else { // IE, используем объект 
selection txt = document.selection.createRange().text; } 
return txt; }

Все браузеры, кроме IE8- поддерживают метод window.getSelection(), который возвращает объект, схожий с рассмотренным ранее Range. У этого объекта есть точка начала выделения (anchor) и фокусная точка окончания (focus). Точки могут совпадать. Рассмотрим свойства и методы объекта Selection:

  • Свойство anchorNode вернет контейнер, в котором начинается выделение. Заметим, что началом выделения считается та граница, от которой вы начали выделение. То есть, если вы выделяете справа налево, то началом будет как раз правая граница. Это правило работает везде, кроме браузера Opera, в котором anchorNode вернет ссылку на узел левого края выделения.
  • Свойство anchorOffset вернет смещение для начала выделения в пределах контейнера anchorNode.
  • Свойства focusNode и focusOffset работают аналогично для фокусных точек, то есть точек окончания выделения. Opera и здесь отличилась, возвращает вместо фокусной точки узел правого края выделения.
  • Свойство rangeCount возвращает число объектов Range, которые входят в полученное выделение. Это свойство полезно при использовании метода addRange.
  • Метод getRangeAt принимает в качестве аргумента индекс объекта Range и возвращает сам объект. Если rangeCount == 1, то работать будет только getRangeAt(0). Таким образом, мы можем получить объект Range, полностью соответствующий текущему выделению.
  • Метод collapse сворачивает выделение в точку (каретку). Методу можно передать в качестве первого аргумента узел, в который нужно поместить каретку.
  • Метод extend принимает в качестве аргументов ссылку на контейнер и смещение (parentNode, offset), и перемещает фокусную точку в это положение.
  • Метод collapseToStart (collapseToEnd) перемещает фокусную границу к начальной, тем самым сворачивая выделение в каретку.
  • Метод selectAllChildren принимает в качестве единственного аргумента ссылку на узел и добавляет всех его потомков в выделение.
  • Метод addRange принимает в качестве аргумента объект Range и добавляет его в выделение. Таким образом можно увеличить количество объектов Range, число которых нам подскажет свойство rangeCount.
  • Метод removeRange (removeAllRanges) удаляет переданный (все) объект Range из выделения.
  • Метод toString вернет текстовое содержимое выделения.

IE предоставляет собственный интерфейс взаимодействия с выделениями – объект selection в контексте document. Для работы с этим объектом используются следующие методы:

  • Метод clear убирает выделение вместе с содержимым.
  • Метод createRange  создает из содержимого выделения TextRange.
  • Метод empty убирает выделение, но оставляет все содержимое.

Надеюсь, теперь, после знакомства с обеими реализациями выделений, код выше стал более понятен.

Установка собственного выделения

Допустим, вам хочется, чтобы какой-то текстовый фрагмент на странице был выделен, как пользовательское выделение. Это нужно при клиентской реализации поиска по странице и некоторых иных задач.

Проще всего решить эту задачу следующим образом:

  1. Создать объект Range (TextRange для IE8-).
  2. Перевести полученный объект в выделение.

Давайте посмотрим реализацию:

<div id="ex5"> Снова будем выделять <span>бабулю</span>, 
на этот раз без поиска. </div> <script>
  function setSelection() { 
var target = document.getElementById('ex5').getElementsByTagName('span')[0]; 
var rng, sel; if (document.createRange) { 
rng = document.createRange(); 
rng.selectNode(target) sel = window.getSelection(); 
sel.removeAllRanges(); sel.addRange(rng); } 
else { 
var rng = document.body.createTextRange(); 
rng.moveToElementText(target); 
rng.select(); } } 
</script>

Снятие выделения

Код для снятия выделения, использующий соответствующие методы объектов Selection:

function clearSelection() { 
try { 
// современный объект Selection 
window.getSelection().removeAllRanges(); } 
catch (e) { 
// для IE8- document.selection.empty(); } }

Итоги

  • В браузерах поддерживается стандартный объект Range
  • В IE8- поддерживается только собственный объект TextRange.

Есть библиотеки, которые «исправляют» объект TextRange, добавляя ему нужные свойства из Range.

Код, получающий выделение, при использовании  библиотеки может выглядеть так:

var range = getRangeObject(); 
if (range) { alert( range ); 
alert( range.startContainer.nodeValue ); 
alert( range.startOffset ); 
alert( range.endOffset ); } 
else { alert( 'Ничего не выделено' ); } }

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Поделиться

Об авторе

admin administrator

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: