Как сделать текст на холсте доступным для выбора?

StackOverflow https://stackoverflow.com/questions/1451635

  •  12-09-2019
  •  | 
  •  

Вопрос

Любое предложение высоко ценится.

Это было полезно?

Решение

Выбор текста имеет множество компонентов: как визуальных, так и невизуальных.

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

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

ctx.fillText("My String", 100, 100);
textWidth = ctx.measureText("My String").width;

Вам все равно придется проанализировать высоту шрифта со свойства «шрифт», так как в настоящее время он не включен в метрики текста.Текст холста по умолчанию выровнен по умолчанию.

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

В этот момент вы знаете, куда должен идти курсор;Вам нужно будет хранить текстовую строку в качестве текстовой строки, конечно, в переменной.

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

В общем, выделение текста и редактирование текста в Canvas довольно трудоемки для программирования, и было бы разумно повторно использовать уже написанные компоненты, отличным примером является Bespin.

Я отредактирую свой пост, если встречу другие публичные примеры.Я считаю, что Беспин использует метод выбора на основе сетки, возможно, требующий моноширинного шрифта.Лигатуры, кернинг, двунаправленность и другие расширенные функции рендеринга шрифтов требуют дополнительного программирования;это сложная проблема.

Другие советы

текст, нарисованный в элементах холста, не может быть выделен из-за особенностей тега холста.Но есть несколько обходных путей, подобных тому, который используется в шрифтJS.

Другим решением было бы добавить текст с позиционированными элементами div вместо использованияstrokeText или fillText.

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

В Canvas нет встроенного механизма выбора текста, поэтому вам придется развернуть собственный код рендеринга и выбора текста, что может быть довольно сложно сделать правильно.

Вы можете почерпнуть некоторые идеи из Беспин.

Они реализовали текстовый редактор на JavaScript с использованием холста с выделением текста, полосы прокрутки, мигание курсора и т. д.

Исходный код

Я бы предложил использовать EaselJS В библиотеке вы можете добавить каждую букву как дочернюю и даже добавить к этому объекту события мыши. Это потрясающая библиотека, посмотрите!

Элементы управления TextInput сложны

Позвольте мне начать с того, что я не являюсь экспертом в текстовых элементах управления, но сейчас я уверен, что это не имеет значения, потому что я могу помочь вам безопасно попасть в лес и выйти из него.Эти вещи сложны по своей природе и требуют большой интуиции и знаний о том, как все работает.Однако вы можете проверить код, который выполняется в senpai-js/senpai-stage репозиторий здесь.

Нам следует заранее определить несколько вещей:

  • Текст может быть любым допустимым символом Юникода.Вы можете проанализировать это, используя этот регулярное выражение: /^.$/u
  • Вам необходимо отслеживать три различных режима редактирования текста: Insert, Selection, Basic (я использую SelectionState enum в моей библиотеке и проверьте insertMode имущество на сцене)
  • Вы должны осуществлять проверки работоспособности на каждом шагу, иначе у вас будет неопределенное и неожиданное поведение.
  • Большинство людей ожидают, что вводимый текст будет иметь размер по ширине, поэтому убедитесь, что вы используете шаблон для внутренней части текстового поля, если планируете использовать текстуру.
  • Обнаружение столкновений мыши/точки касания является сложным, если вы не гарантируете, что элемент управления вводом текста не будет вращаться
  • Текст должен прокручиваться, если он больше текстового поля в горизонтальном направлении.Мы будем называть это textScroll которое всегда является отрицательным числом

Теперь я рассмотрю каждую функцию, чтобы описать ее поведение и точно описать, как должен работать элемент управления текстовым полем.

Столкновение (широкая фаза и узкая фаза)

Обнаружение столкновений — это монстр.Нормализация перемещения точки между событиями мыши и касания — сложная задача, не описанная в этом тексте.После того, как вы обработаете точечные события, вам придется выполнить своего рода общее обнаружение столкновений для прямоугольника.Это означает столкновение AABB.Если сам спрайт текстового поля повернут, вам придется «отменить поворот» самой точки.Однако мы обходим эту проверку, если точка мыши/прикосновения уже находится над текстовым полем.Это связано с тем, что как только вы начнете выбирать текст, вы хотите, чтобы эта функция всегда возвращала true.Затем мы переходим к столкновению с узкой фазой, которое фактически проверяет, находится ли «нетрансформированная» точка мыши/прикосновения внутри поля текстового поля.Если это так или текстовое поле активно, мы возвращаем здесь истинное значение.

Как только мы узнаем, что точка мыши/прикосновения находится в пределах нашего текстового поля, мы меняем CSS холста на cursor: text; визуально.

точкаСтолкновение

Когда мы нажимаем кнопку мыши на текстовое поле, нам нужно рассчитать, куда переместить курсор.Каретка может существовать в диапазоне от 0 к text.length включительно.Обратите внимание, что это не совсем верно, поскольку символы Юникода могут иметь длину 2.Вы должны отслеживать каждый символ, добавленный в ваш текст внутри массива, чтобы убедиться, что вы не измеряете ошибочные символы Юникода.Вычисление целевого индекса означает циклический просмотр каждого символа текущего текста и добавление его к временной строке, измерение каждый раз до тех пор, пока измеренная ширина не станет больше, чем текущий textScroll + измеренный textWidth.

Как только мы убедимся, что точка опустилась поверх текстового поля и установлена ​​начальная точка, мы можем запустить режим «выбора».Перетаскивание точки должно переместить выделение из начального индекса курсора в новый рассчитанный конечный индекс.Это происходит в обоих направлениях.

Пример этого показан здесь.

Нажатия клавиш

Решением проблемы с нажатием веб-клавиш является проверка key свойство KeyEvent.Несмотря на то, что все говорят, это свойство текста можно проверить, проверив его на соответствие вышеупомянутому регулярному выражению Юникода.Если оно совпадает, скорее всего, клавиша действительно была нажата на клавиатуре.Это не учитывает такие комбинации клавиш, как ctrl + c и ctrl + v для копирования и вставки.Эти функции тривиальны и оставлено на усмотрение читателя, как их реализовать.

Несколькими исключениями являются клавиши со стрелками:«Стрелка влево», «Стрелка вправо» и т. д.Эти клавиши фактически изменяют состояние вашего элемента управления и меняют его работу.Важно помнить, что ключевые события должны обрабатываться только текущими focused контроль.Это означает, что вам следует проверить и убедиться, что элемент управления сфокусирован во время ввода текста.Это, конечно, происходит на более высоком уровне, чем тот, который я запрограммировал в своей библиотеке, так что это тривиально.

Следующая проблема, которую необходимо решить, заключается в том, как каждый ввод символов должен изменять состояние вашего элемента управления.А keyDown метод различает selectionState и вызывает другую функцию в зависимости от ее состояния.Это не оптимизированный псевдокод, но он используется для ясности и идеально подходит для наших целей при описании поведения.

клавиша при выборе

  • Обычные нажатия клавиш заменяют содержимое выделенного текста.
  • Сращивание из selectionStart, и вставьте новый ключ в текстовый массив
  • если нажата «удалить» или «возврат», соединить выделение и вернуть режим выделения в Normal или Caret
  • если нажата клавиша «влево» или «вправо», переместите курсор в начало или конец соответственно и верните режим выбора в «Обычный», если не нажата клавиша «Shift»
  • если нажата клавиша Shift, то мы на самом деле хотим расширить выделение дальше
    • Начало выделения всегда будет с индекса курсора, и мы, по сути, перемещаем конечную точку выделения влево или вправо с помощью этой комбинации клавиш.
    • если конец выделения возвращается обратно к индексу каретки, мы возвращаем selectionState к Normal снова
  • клавиши «домой» и «конец» работают одинаково, только курсор перемещается на 0 и text.length индексы соответственно
    • Также обратите внимание, что удержание клавиши Shift расширяет выбор caretIndex снова

нажатие клавиши в обычном режиме (режим курсора)

  • в режиме каретки мы не заменяем текст, просто вставляем новые символы в текущую позицию
  • нажатия клавиш, соответствующие регулярному выражению Юникода, вставляются с использованием метода сращивания
  • переместите курсор вправо после объединения текста (проверьте и убедитесь, что вы не превышаете длину текста)
  • Backspace удаляет один символ перед индексом в caretIndex - 1
  • Удалить удаляет один символ после индекса в caretIndex
  • выделение текста применяется для левой и правой клавиш, пока нажата клавиша Shift
  • когда Shift не нажат, влево и вправо перемещают курсор влево и вправо соответственно
  • клавиша Home устанавливает для CaretIndex значение 0
  • конечная клавиша устанавливает для CaretIndex значение text.length

keyDown в режиме вставки

  • в режиме вставки мы заменяем текущий выбранный символ на caretIndex
  • нажатия клавиш, соответствующие регулярному выражению Юникода, вставляются с использованием метода сращивания
  • переместите курсор вправо после объединения текста (проверьте и убедитесь, что вы не превышаете длину текста)
  • Backspace удаляет символ ПЕРЕД текущим выбором
  • delete удаляет текущий выбранный символ
  • клавиши со стрелками работают как положено и описаны в обычном режиме
  • клавиши Home и End работают как положено и описаны в обычном режиме

обновление текстового поля каждый кадр

  • Если текстовое поле находится в фокусе, вам следует начать мигать курсором, чтобы пользователь знал, что он редактирует текст в текстовом поле.
  • при перемещении курсора влево или вправо Caret режиме, вам следует перезапустить механизм вспышки, чтобы он точно показывал, где они находятся, каждый раз, когда перемещается курсор
  • Мигайте кареткой примерно раз в 30 кадров или полсекунды.
  • измерьте, насколько далеко курсор находится вдоль текста, используя ctx.measureText к индексу каретки, разрезая текст до позиции каретки, если режим не Selection
  • По-прежнему полезно измерять, как далеко находится текст в режиме выделения. Selection, потому что мы всегда хотим, чтобы конец выделенного текста был виден пользователю.
  • Убедитесь, что курсор всегда виден в пределах видимых границ текстового поля, принимая во внимание текущий textScroll.

рендеринг текстового поля

  • сначала сохраните контекст ctx.save() (основное полотно)
  • если вы не рисуете текстовое поле с контурами, нарисуйте левую крышку текстового поля, нарисуйте средний узор и правую крышку соответственно на первом слое.
  • используйте путь, определяемый отступами и размером текстового поля, чтобы вырезать квадрат и предотвратить вытекание текста
  • перевести на х textScroll значение, которое должно быть отрицательным числом
  • переведи на y midline значение, которое должно находиться в середине текстового поля по вертикали
  • установить свойство шрифта
  • установите базовую линию текста на middle и заполните текст, позвонив text.join("") в вашем текстовом массиве
  • если есть режим выделения или вставки, обязательно нарисуйте «синий» квадрат позади выделенного текста и инвертируйте цвет шрифта выделенного текста (это нетривиально и оставлено читателю в качестве упражнения)

Заключительные замечания

Да, задача чудовищная, но иногда хочется просто подарить миру движок визуальной новеллы или спрайтовую сцену со всякими возможностями.Пожалуйста, посетите senpai-stage репо и отправьте запрос на включение, если чувствуете, что хотите, чтобы сенпай вас заметил.Удачи в ваших начинаниях!

~дьявол сэмпай

FabricJS теперь имеет возможность взаимодействовать с объектами вне элемента холста, например это демо показывает кнопку, связанную с изображением, загруженным в элемент холста.

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

Холст — это просто поверхность для рисования.Вы выполняете рендеринг, и результат — пиксели.Итак, вам нужно будет отслеживать позиции всего текста, который вы отображаете на холсте, в какой-то структуре данных, которую вы будете обрабатывать во время событий мыши.

Простой ответ будет:либо используйте HTML или SVG вместо холста.Если только вам действительно не нужна степень, которую предлагает холст низкого уровня управления.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top