Как стать автором
Обновить

Текстовый Инпут с возможностью выделять отдельные слова

Уровень сложностиСредний
Время на прочтение3 мин
Количество просмотров2.6K

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

Для нетерпеливых

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

Очевидное решение — это сделать все обычным input»ом с абсолютно позиционированным div»ом на заднем фоне. Скрыть текст самого поля, оставив только каретку, и сделать видимым только содержимое заднего блока. Так я уже добился того, что у меня был полностью стилизуемый текст.

Стили включать не буду, так как это займет слишком много места. Принцип прост: задать одинаковые размеры, отступы и шрифты для div»а и input»а.

const [value, setValue] = useState('');

const onChange = (e) => setValue(e.target.value);
const format = (text) => // formatted jsx
<div className="container">
  <div className="text">{format(value)}</div>
  <input type="text" className="field" value={value} onChange={onChange} />
</div>

Первая проблема

При введении длинной строки поле начинает скроллиться, а текст в блоке стоит на своем месте.
При введении длинной строки поле начинает скроллиться, а текст в блоке стоит на своем месте.


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

// ...
const textBlockRef = useRef(null);
const [scrollValue, setScrollValue] = useState(0);
// ...
const handleScroll = (e) => setScrollValue(e.currentTarget.scrollLeft);

useEffect(() => {
  if (textBlockRef.current) {
    textBlockRef.current.scrollLeft = scrollValue;
  }
}, [scrollValue]);
<!-- ... -->
<div className="text" ref={textBlockRef}>{format(value)}</div>
<input
  type="text"
  className="field"
  value={value}
  onChange={onChange}
  onScroll={handleScroll}
 />
<!-- ... -->


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

Вторая проблема

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

В браузере Safari не работает свойство onScroll у простого input’а.

Начал было я сомневаться в правильности своего подхода, как выяснилось, что safari поддерживает onScroll у textarea. Это решило проблему. Оставалось только стилизовать textarea, чтобы он выглядел и вел себя как простой input[type=”text”], и дело с концом.

<!-- ... -->
<textarea
  className="field"
  ref={ref}
  value={value}
  onChange={onChange}
  onScroll={handleScroll}
  rows={1}
/>
<!-- ... -->

Третья проблема (более очевидная)

При нажатии на Enter, в textarea происходил перенос строки. Это решается просто - нужно отловить нажатие и отменить его, плюс, если инпут находится в форме, сделать сабмит.

// ...
  const submitHandler = (e) => {
    if (e.keyCode === 13 || e.key === "Enter" || e.which === 13) {
      if (!e.repeat && e.target.form) {
        e.target.form.submit();
      }
      e.preventDefault();
    }
  };
// ...
<!-- ... -->
<textarea
  className="field"
  ref={ref}
  value={value}
  onChange={onChange}
  onScroll={handleScroll}
  onKeyDown={submitHandler}
  rows={1}
/>
<!-- ... -->

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

Финальный результат можно посмотреть на CodeSandbox.


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

Теги:
Хабы:
Всего голосов 4: ↑3 и ↓1+3
Комментарии5

Публикации

Истории

Работа

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн
10 – 11 октября
HR IT & Team Lead конференция «Битва за IT-таланты»
МоскваОнлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн