Рано или поздно вы получите от дизайнера макет, где встретится он — с виду вроде бы обычный текстовый инпут, а на деле, меняющий свою ширину или высоту, в зависимости от содержимого. Хорошие новости — отчаиваться не будем, это возможно. Причем, возможно достаточно разнообразными способами, об одном из них сегодня и поговорим.
Проблема
Прежде чем кидаться решать проблему — ее нужно осознать. С чего бы начать? Все зависит от поставленной вам задачи. Например: если требуется, чтобы инпут расширялся в ширину — это одна проблема. Если нужно чтобы он рос в высоту — это проблема другая. А если нужно чтобы сразу во все стороны, тут можно начинать страдать, дело-то непростое.
Главная проблема состоит в том, что элементы ввода не умеют растягиваться по своему содержимому. Это очень досадно и конечно мир был бы лучше, если бы они умели, но увы. Более того, однострочное текстовое поле — 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
Приходилось вам сталкиваться с подобной задачей? Как решали?