Как быстро получить позицию каретки в HTML-документе 1С при использовании движка WebKit

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Переход платформы 1С:Предприятие с устаревшего движка Internet Explorer (MSHTML) на современный WebKit (Chromium) принес множество преимуществ, но одновременно с этим сделал нерабочими старые методы манипуляции с текстом в полях HTML-документа. Одной из наиболее острых проблем стал поиск позиции каретки (курсора) относительно начала текста. В этой статье мы разберем, почему старые подходы больше не работают, в чем причина низкой производительности стандартных функций 1С при работе с DOM и как реализовать мгновенное получение координат каретки.

Причина низкой производительности: почему «тупой» обход узлов — это плохо

Рассмотрим ситуацию, с которой сталкиваются многие разработчики: попытка перебрать все узлы документа (NodeList) рекурсивной функцией на языке 1С для подсчета суммарной длины текста. Как показывает практика, обход всего 1000 узлов может занимать около 400 мс, что недопустимо для интерактивного интерфейса. Проанализируем причину такой задержки.

Основная проблема заключается не в самом алгоритме перебора, а в «стоимости» каждого обращения из кода 1С к объектам HTML-документа. Когда мы используем конструкцию типа Для Каждого Потомок Из Узел.childNodes Цикл, происходит следующее:

  1. 1С обращается к программному интерфейсу (мосту) между платформой и движком браузера.
  2. Движок WebKit подготавливает данные и передает их в среду 1С.
  3. Происходит маршалинг данных (преобразование типов).

Если в документе сотни или тысячи узлов, эти микрозадержки суммируются в ощутимые доли секунды. Для решения этой задачи нам необходимо либо минимизировать количество обращений, либо перенести логику обхода целиком на сторону JavaScript.

Эффективное решение: использование объекта Range

В старых версиях 1С (IE) использовался метод createTextRange(), который позволял легко манипулировать диапазонами. В WebKit ему на смену пришел стандарт DOM Range API. Чтобы получить количество символов от начала документа до позиции каретки без перебора всех узлов, мы воспользуемся объектом Selection и создадим диапазон, охватывающий текст от начала body до точки фокуса.

Рассмотрим оптимальный алгоритм, который работает практически мгновенно:


// Получаем объект окна и текущее выделение
Окно = Элементы.ПолеHTML.Документ.defaultView;
ТекущееВыделение = Окно.getSelection();

// Создаем новый диапазон для измерения текста ДО каретки
Диапазон = ДокументHtml.createRange();

// Важно: явно устанавливаем начало в body, чтобы избежать захвата служебных областей
Диапазон.setStart(ДокументHtml.body, 0);

// Устанавливаем конец диапазона в позицию, где сейчас находится курсор
Диапазон.setEnd(ТекущееВыделение.anchorNode, ТекущееВыделение.anchorOffset);

// Метод toString() объекта Range возвращает видимый текст в этом диапазоне
ТекстДоКаретки = Диапазон.toString();
Начало = СтрДлина(ТекстДоКаретки);

Важное замечание: В некоторых случаях объект Range сразу после создания может указывать на мета-данные перед началом тега body. Чтобы гарантировать точность подсчета, всегда явно вызывайте setStart(ДокументHtml.body, 0). Это исключит попадание «мусорных» символов разметки в итоговую строку.

Оптимизация через JavaScript-вставки

Несмотря на то, что код выше работает значительно быстрее прямого перебора, мы можем еще больше ускорить выполнение и повысить надежность, если перенесем вычисления полностью в среду выполнения JavaScript. Это избавит нас от необходимости гонять текстовые данные (которые могут быть очень объемными) между браузером и 1С.

Рекомендуется создать на стороне JS функцию, которая будет возвращать уже готовое число — длину текста. Выясним причину, почему это лучше: 1С и JavaScript могут по-разному обрабатывать спецсимволы и переводы строк. Выполняя .length внутри JS, мы получаем гарантированную синхронность с тем, как документ видит сам браузер.


// Пример JS-кода, который можно внедрить в страницу
function getCaretCharacterOffset() {
    var selection = window.getSelection();
    if (selection.rangeCount > 0) {
        var range = selection.getRangeAt(0);
        var preCaretRange = range.cloneRange();
        preCaretRange.selectNodeContents(document.body);
        preCaretRange.setEnd(range.endContainer, range.endOffset);
        return preCaretRange.toString().length;
    }
    return 0;
}

Вызов этой функции из 1С через Элементы.ПолеHTML.ВыполнитьJavaScript("getCaretCharacterOffset()") вернет число за несколько миллисекунд.

Как восстановить выделение по смещению (обратная задача)

После того как мы получили числовое смещение, часто возникает задача вернуть курсор в ту же позицию (например, после обновления содержимого HTML). Это более сложная задача, так как setStart требует указания конкретного узла, а не просто числа от начала документа.

Для решения проанализируем использование объекта TreeWalker. Это встроенный в WebKit эффективный итератор узлов, который работает быстрее любого самописного перебора.

  1. Создаем TreeWalker, настроенный только на текстовые узлы (NodeFilter.SHOW_TEXT).
  2. Перебираем узлы по порядку, суммируя их длину.
  3. Как только сумма длин превысит наше искомое смещение, значит, каретка находится в текущем узле.
  4. Вычисляем локальное смещение внутри этого узла: НужноеСмещение - СуммаДлинПредыдущих.
  5. Устанавливаем фокус через Range.setStart(НайденныйУзел, ЛокальноеСмещение).

Такой подход считается «золотым стандартом» при работе с contentEditable элементами в современных веб-движках, используемых в 1С.

Нюансы подсчета символов в 1С и JS

При работе с текстом важно учитывать разницу между textContent и innerText. Свойство textContent (и метод toString() у Range) возвращает содержимое всех узлов, включая скрытые скрипты и стили, если они попали в диапазон. Свойство innerText более «умное» — оно учитывает CSS-стили и возвращает текст так, как его видит пользователь (игнорируя display: none). В большинстве задач по разработке редакторов в 1С следует ориентироваться на textContent, так как он обеспечивает более предсказуемое поведение при манипуляции узлами DOM.

Резюмируем: для быстрой работы с HTML в 1С на движке WebKit забудьте о рекурсивном переборе узлов средствами 1С. Используйте Range для получения данных и TreeWalker внутри JavaScript-функций для сложной навигации. Это обеспечит мгновенный отклик интерфейса даже в документах объемом в несколько мегабайт.

← На главную