Как измерить позицию слова/каретки в Документах Google?

Для тех, кто не работал с редактором Google Docs, вот краткое объяснение того, как он работает:

  • Документы Google не имеют видимых редактируемых элементов textarea или contentEditable.
  • Google Docs прослушивает нажатие/нажатие клавиши/вверх в отдельном iFrame, куда они помещают курсор ОС для прослушивания событий.
  • Когда iFrame перехватывает событие, Google обрабатывает его, выполняя аналогичные операции с видимым документом.
  • «Каретка» в Google Docs — это DIV, который оформлен и написан таким образом, чтобы выглядеть и действовать как курсор ОС.

С этим из пути, вот моя просьба:

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

  • Выделите слова непрозрачным наложением DIV.
  • Определить положение курсора внутри слова.

Я исчерпал множество идей о том, как с этим справиться, но пока мне удалось найти решение с ошибками только для последней проблемы (я выполняю возврат, определяю, где изменился текст, и отменяю возврат).

Я ищу все лучшие идеи, которые вы можете придумать, чтобы решить эти проблемы. Они не должны быть кросс-браузерными, но их нужно превратить во что-то надежное, что также будет обрабатывать такие вещи, как изменение размера шрифта в средней строке.

Немного дополнительной информации, объясняющей, как выглядит Google Doc в HTML:

<wrapper> // Simplified wrapper containing margins, pagination and similar
  <div class="kix-paragraphrenderer"> // single DIV per page wrapping all content
    // Multiple paragraphs separated by linebreak created by Enter key:
    <div class="kix-paragraphrendeder">...</div>
    <div class="kix-paragraphrendeder">...</div>
    <div class="kix-paragraphrendeder">
      // Multiple wrapper divs created by Google's word wrapping:
      <div class="kix-lineview">...</div>
      <div class="kix-lineview">...</div>
      <div class="kix-lineview">
        // Single inner wrapper, still full width of first wrapper paragraph:
        <div class="kix-lineview-content">
          // Single wrapper SPAN containing full text of the line, but not display:block
          <span class="kix-lineview-text-block">
            // Multiple spans, one per new font change such as normal/bold text,
              // change in font size, indentation and similar:
            <span>This is normal text</span>
            <span style="font-size:40px; padding-left:4px;">This larger text.</span>
            <span style="font-weight:bold; padding-left:10px;">This is bold text</span>
            <span style="padding-left:4px;">More normal text</span>
          </span>
        </div>
      </div>
    </div>
  </div>
</wrapper>

person Woodgnome    schedule 10.04.2012    source источник


Ответы (2)


После долгих размышлений я пришел к выводу, что крайне проблематично — если не невозможно — пытаться программно определить позицию курсора относительно буквы внутри <span> просто потому, что <span> — это наименьший элемент, который можно измерить (поправьте меня, если я ошибаюсь). ошибаюсь).

Итак, как решить проблему? Вот что я в итоге сделал:

  • Я создаю закадровый позиционированный <div>
  • Я получаю текст текущего абзаца (<div class="kix-paragraphrenderer">) — я мог получить весь текст, но хотел ограничить вычислительную нагрузку.
  • I extract each single character of the paragraph by looping through its children in the following way:
    • Loop through linveviews of the paragraph (<div class="kix-lineview">)
    • Получить содержимое линейного просмотра (<div class="kix-lineview-content">)
    • Перебирать текстовые блоки содержимого линейного просмотра (<span class="kix-lineview-text-block">)
    • Перебрать <span> текстового блока
    • Перебрать innerText из <span>
    • Я добавляю к каждому символу вне экрана <div> стиль, примененный в данный момент, извлеченный из style.cssText текущего <span>
    • Для каждого добавленного символа я измеряю ширину <div> и сохраняю ее в массиве. Теперь у меня есть позиция каждого отдельного символа.
  • Я измеряю положение курсора относительно своей ширины и вуаля — я знаю, где находится курсор в тексте.

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

Это работает довольно хорошо, но есть много подводных камней и требуется много измерений. Вдобавок ко всему, также требуется пост-анализ текста, если вы хотите использовать его для чего-либо, поскольку табуляция, пробелы и разрывы строк не всегда включаются в innerText (в зависимости от того, где они находятся в тексте, Google может или не может сделать их за счет позиционирования новых элементов).

person Woodgnome    schedule 18.05.2012

Я сделал что-то вроде Kix два года назад Google Docs. И для любого HTML-дизайна, и да, для IE6 тоже :-) Как? Все, что нам нужно, это вычислить абсолютную позицию буквы. Как? Замените textNode встроенным элементом без макета, это важно, а затем используйте Element.getClientRects. Я помню, мне также нужно было обернуть только букву и вычислить ее позицию с помощью быстрого и надежного https://developer.mozilla.org/en-US/docs/Web/API/Element.getBoundingClientRect

Трюк с определением линий и переносов для клавиш home и end основан на некотором эвристическом изменении положения букв по вертикали. Что-то вроде того, если базовая линия отличается, чем остановить ходьбу по каретке. Это было довольно быстро и с любой разметкой и без какого-либо кэширования. Святой Грааль :)

Единственной неразрешимой проблемой было выравнивание текста, т.к. буквы распределялись случайным образом и пробелы между ними не вычислялись.

Этот проект http://webeena.com мертв. Плохой менеджмент убил его (и меня почти тоже).

person Daniel Steigerwald    schedule 29.01.2014
comment
Не уверен, что понимаю, что вы имеете в виду, заменяя textNode, но после моего ответа я обновил свой код, чтобы сделать что-то похожее на то, что вы предлагаете. В отличие от изменения любого HTML, я создаю объект Range и манипулирую начальным и конечным узлами, пока Range.getBoundingClientRect() не сможет сказать мне, где находится курсор, по сравнению с cursor.getBoundingClientRect(). - person Woodgnome; 30.01.2014