Как сделать текст на холсте выбираемым? - PullRequest
18 голосов
/ 20 сентября 2009

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

Ответы [ 9 ]

22 голосов
/ 04 октября 2009

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

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

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

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

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

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

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

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

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

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

5 голосов
/ 20 сентября 2009

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

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

3 голосов
/ 10 октября 2018

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

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

Мы должны определить несколько вещей заранее:

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

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

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

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

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

pointCollision

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

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

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

нажатия

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

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

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

нажатие клавиши выбора

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

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

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

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

  • в режиме вставки мы заменяем текущий выбранный символ на caretIndex
  • нажатия клавиш, соответствующие регулярному выражению Юникода, вставляются с использованием метода сращивания
  • переместите каретку вправо после сращивания текста (проверьте и убедитесь, что вы не перебираете длину текста)
  • Забой удалить символ ДО текущего выбора
  • delete удаляет текущий выбранный символ
  • клавиши со стрелками работают как положено и описаны в обычном режиме
  • клавиши home и end работают как положено и описаны в обычном режиме

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

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

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

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

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

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

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

3 голосов
/ 11 сентября 2012

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

3 голосов
/ 21 сентября 2009

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

Они реализовали текстовый редактор в javascript, используя canvas с выделением текста , полосы прокрутки, мигание курсора и т. Д.

Исходный код

3 голосов
/ 20 сентября 2009

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

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

2 голосов
/ 01 сентября 2012

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

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

1 голос
/ 22 сентября 2009

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

1 голос
/ 20 сентября 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...