Очередной блог фрилансера

коротко и полезно о веб-разработке

Menu
  • О чем это все
Menu

Как сделать адаптивный по высоте/ширине инпут

20.12.202020.12.2020

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

Проблема

Прежде чем кидаться решать проблему — ее нужно осознать. С чего бы начать? Все зависит от поставленной вам задачи. Например: если требуется, чтобы инпут расширялся в ширину — это одна проблема. Если нужно чтобы он рос в высоту — это проблема другая. А если нужно чтобы сразу во все стороны, тут можно начинать страдать, дело-то непростое.

Главная проблема состоит в том, что элементы ввода не умеют растягиваться по своему содержимому. Это очень досадно и конечно мир был бы лучше, если бы они умели, но увы. Более того, однострочное текстовое поле — input вообще бессмысленно растягивать в высоту, содержимое все равно не ляжет в несколько строк, так уж он устроен. Поэтому дальше начинаются любимые фронтендерами лайфхаки.

Варианты решения

Самый первый, приходящий в топ выдачи гугла — это просто взять и заменить инпут на div с атрибутом contenteditable  — и вот оно, проблема решена! Вот вам адаптивный по высоте/ширине див и редактируемое содержимое. Но если для вас (как и для меня) это не вариант, то смотрим дальше.

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

Ну и третий вариант, который мне больше всего пришелся по душе — это все-таки вычислять содержимое текстового инпута и адаптировать его ширину/высоту, чтобы все помещалось. Давайте на него посмотрим чуть более внимательно.

Выбираем правильный инпут

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

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

.textarea {
  font-size: 24px;
  font-family: Arial;
  border-radius: 8px;
  resize: none;
  padding: 5px;
  overflow: hidden;
  box-sizing: border-box;
  height: 40px;
  min-height: 40px;
  width: 400px;
  margin-bottom: 15px;
}

Тут следует обратить внимание на три обязательных параметра, без которых ничего не сработает: box-sizing, height, min-height.

box-sizing: border-box

В обязательном порядке box-sizing должен быть в значении border-box, чтобы когда вы добавите паддинг в текстовом поле, а вы его обязательно добавите, он не увеличивал дополнительно высоту или ширину текстового поля, а откладывался бы вовнутрь.

height и min-height (width и min-width)

Тут все просто: height — чтобы изначально textarea выглядела бы как input. min-height — чтобы при сбросе значения высоты текстовое поле не прыгало, а оставалось минимально необходимого размера. Все это справедливо и для свойств width и min-width.

Немного теории

Принцип действия очень простой. У каждого DOM-элемента, который может содержать контент, есть readonly-свойство scrollHeight (scrollWidth) которое и содержит так нужную нам, истинную высоту элемента, такую, при которой все содержимое этого элемента будет видно пользователю. Все, что нам требуется — при изменении текстового поля, вычислять значение scrollHeight/scrollWidth и присваивать его текстовому полю.

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

Немного vanilla JS-практики

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

const textareaHeight = document.getElementById("text");

textareaHeight.addEventListener("input", (event) => {
  textareaHeight.style.height = 0;
  textareaHeight.style.height = textareaHeight.scrollHeight + "px";
})

Это ввод с клавиатуры. Не стоит забывать, что текстовые поля бывают заполняются автоматически, извне. Для этого добавляем отдельный метод вставки значения:

function setValue(text: string) {
  const textarea = document.getElementById("text");
  textarea.style.height = 0;
  textarea.value = text;
  textarea.style.height = textarea.scrollHeight + "px";
}

Ну и давайте для ширины тоже сделаем. Все то же самое, только тут уже берем input, и высоту меняем на ширину.

const textInput = document.getElementById("text");

textInput.addEventListener("input", (event) => {
  textInput.style.width =  0;
  textInput.style.width = textInput.scrollWidth + "px";
})

function setValue(text: string) {
  const textInput = document.getElementById("text");
  textInput.style.width = 0;
  textInput.value = text;
  textInput.style.width = textInput.scrollWidth + "px";
}

Результат

Вот такие поля получились. Несложно и недолго.

Адаптивный инпут, растущий в высоту:

See the Pen Adaptive Height Text Input with Vanilla JS by dreamhelg (@dreamhelg) on CodePen.dark

Адаптивный инпут, растущий в ширину:

See the Pen Adaptive Width Text Input with Vanilla JS by dreamhelg (@dreamhelg) on CodePen.dark

Angular Директива

Для тех, кто дочитал аж до сюда — бонус. Если лень самостоятельно упаковать все это в директиву, я уже все сделала за вас, смотрите:


@Directive({
  selector: "[adaptiveInputDirective]"
})
export class AdaptiveInputDirective implements OnInit {
  @Input() horizontal: boolean;

  constructor(private element: ElementRef) {}

  ngOnInit() {
    if (this.horizontal) {
      this.element.nativeElement.style.whiteSpace = "nowrap";
    }
  }

  @HostListener("ngModelChange", ["$event"])
  onChange(): void {
    const input = this.element.nativeElement;

    if (this.horizontal) {
      input.style.width = 0;
      input.style.width = input.scrollWidth + "px";
    } else {
      input.style.height = 0;
      input.style.height = input.scrollHeight + "px";
    }
  }
}

Здесь используется уже знакомый вам декоратор @HostListener. А если еще не знакомый, то обязательно почитайте статью об этом.

Пример использования:

<textarea formcontrolname="myText" adaptiveinputdirective=""></textarea>

Да, можно обойтись одним элементом — textarea. Просто, если нужна адаптивность в ширину — добавим CSS-свойство white-space: nowrap — запрет на перенос строк.

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

Посмотреть как работает на stackblitz.com

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

Добавить комментарий Отменить ответ

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Об авторе

avatar

Ольга Фомина

Senior Frontend Developer
Почтовые технологии
telegram youtube instagram vkontakte

Рубрики

  • angular
  • JavaScript
  • nodejs
  • svg
  • wordpress
  • верстка
  • изучаем Jquery
  • общая
  • переводы
  • плагины Jquery
  • юнит-тесты
© 2023 Очередной блог фрилансера | Powered by Superbs Personal Blog theme