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

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

Проблема

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

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

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

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

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

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

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

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

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

[code lang=”css”] .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; } [/code]

Тут следует обратить внимание на три обязательных параметра, без которых ничего не сработает: 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

[code lang=”javascript”] const textareaHeight = document.getElementById("text"); textareaHeight.addEventListener("input", (event) => { textareaHeight.style.height = 0; textareaHeight.style.height = textareaHeight.scrollHeight + "px"; }) [/code]

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

[code lang=”javascript”] function setValue(text: string) { const textarea = document.getElementById("text"); textarea.style.height = 0; textarea.value = text; textarea.style.height = textarea.scrollHeight + "px"; } [/code]

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

[code lang=”javascript”] 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"; } [/code]

Результат

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

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

[codepen_embed height=”265″ theme_id=”dark” slug_hash=”MWebvEV” default_tab=”js,result” user=”dreamhelg”]See the Pen Adaptive Height Text Input with Vanilla JS by dreamhelg (@dreamhelg) on CodePen.[/codepen_embed]

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

[codepen_embed height=”265″ theme_id=”dark” slug_hash=”JjRyozz” default_tab=”js,result” user=”dreamhelg”]See the Pen Adaptive Width Text Input with Vanilla JS by dreamhelg (@dreamhelg) on CodePen.[/codepen_embed]

Angular Директива

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

[code lang=”javascript”] @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"; } } } [/code]

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

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

[code lang=”html”] <textarea formcontrolname="myText" adaptiveinputdirective=""></textarea> [/code]

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

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

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

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


Posted

in

, ,

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *