Делаю проект в связке next.js 16 + tailwind v4. Это просто обертка над llm под капотом (если не углубляться). На днях пытался сделать форму ввода как в веб-версии chatGPT. Изображения прикрепил ниже, вживую
тут.
В какой-то момент пришел к выводу, что наилучшим решением будет делать гридами. Ещё понял, что нужно заводить два как бы кадра, между которыми будет происходить анимация, пускай называются до/после. Для флага, который переключает эти кадры заведем состояние isExpanded. Теперь получается вот такая разметка, если рассмотреть ее по грид-ареа:
- первый кадр (isExpanded === false) --> у нас в грид сетке 1 ряд, 3 колонки, выглядит вот так:
| button | textarea | button button |
это повторяет разметку обычной формы ввода, которую сейчас пихают в ai приложения. внутри формы слева-направо: кнопка с иконкой, поле ввода (textarea, contenteditable элемент), контейнер, внутри которого еще две кнопки с иконкой.
- второй кадр (isExpanded === true) --> в грид сетке становится 2 ряда и 3 колонки, выглядит вот так:
| textarea | textarea | textarea |
| button | тут пусто| button button |
это уже разложенное состояние. в целом все понятно - сверху широкое поле ввода, а снизу остается ряд с кнопками для взаимодействия.
Все хорошо, но что должно триггерить изменение значения у isExpanded? Кажется, что все просто, нам ведь известна ширина элемента, она стабильна, потому что грид-ареа textarea стоит статично между двумя блоками, а родитель ограничен по ширине. То есть когда контент в textarea достигает границы ширины, то происходит перенос строки, далее textarea вырастет в высоту в два раза, потому что начнется вторая строка. Будто можно брать этот момент за флаг, который будет переключать isExpanded в состояние true.
Тут не знаю, стоит ли пояснять, но по начальной задумке одна строка в текстаре равна по высоте всем остальным элементам формы (как в chatGPT), все стоит ровно, горизонтально и классно.
Тут случится проблема - isExpanded переключилось и, соответственно, вся форма поменяла грид-сетку на состояние второго кадра и ширина грид-ареа textarea увеличилась, потому что элемент теперь в верхнем ряду, а значит к нему добавилась ширина других двух колонок грида. Вот тут самый прикол - при вводе следующего символа состояние isExpanded снова изменится, потому что теперь первая строка символов норм помещается в этот ряд, а потом еще раз так, и еще раз. И с каждым таким переключение isExpanded будет переключаться, форму будет переключать между первым и вторым кадром туда-сюда. В какой-то момент предел будет достигнут и форма статично встанет во второй кадр. Такое не пойдет.
Вот тут кажется логичным и простым решением зафиксировать флаг isExpanded в состояние true в самый первый момент изменения высоты textarea, чтобы форма зафиксировалась во втором кадре и дальше такой и оставалась, а при удалении символов можно просто сбрасывать обратно первый кадр, когда удалены все до последнего символы в поле ввода.
Вроде бы схема рабочая, подумал я и пошел делать. Далее встал вопрос анимации, потому что до текущего момента это было просто слайд-шоу из двух кадров. А анимировать я не сумел, только очень коряво. Я пробовал анимировать гриды, потому что знал, что вроде как их можно анимировать нативным css, но выходило очень некрасиво, кнопки могли терять форму (сжаться/развернуться) в процессе, текст перескакивал невнятно и очень неуверенно. Я далее попробовал поиграться с паддингами у textarea, вроде выровнял таким образом, что сдвига текста сильно не видно и при этом сама форма выглядит органично.
В целом результат получился на 5/10. Очень странные и смешаные чувства испытал я в этот момент и пошел искать другие решения. Вспомнил про библиотеку framer, которая умеет анимировать изменения лейаута. Зашел в документацию, прочитал, подумал, начал делать. В итоге пришлось все равно долго разбираться в настройке анимации, хотя сперва думал, что минут 30 это максимум займет. Настроил изменение лейаута через этот же пропс layout на каждом элементе и вроде как сработало. Выглядело лучше, чем анимация на чистом css, но все равно очень тупо и неказисто.
Вот тут и встает вопрос, а как можно реализовать форму ввода, которая будет повторять поведение формы ввода как в веб-версии chatGPT? Если у кого-то есть хорошие идеи, предложения, рекомендации, примеры кода, какие-то пояснения, хоть что-то -
дайте знать, пожалуйста.
Ещё важный момент - поле ввода, именно куда вводится текст будущего промпта, может быть реализовано по сути либо элементом textarea, либо же contenteditable div, потому что оба имеют возможность расти в высоту. Я и попробовал, но результат был плюс-минус таким же. Большую часть времени я все-таки тестил это дело с textarea внутри, потому что в chatGPT используется именно contenteditable компонент, потому что есть возможность вставлять туда изображения и прикреплять вложения. В мои задачи это не входило, потому что я не планирую добавлять этот функционал, хоты бы пока. Поэтому самым простым решением и было вставить textarea. Тут ещё просто для справки стоит сказать, что в gpt все-таки учтена адаптивность, поэтому не помню на каком разрешении contenteditable элемент меняется на обычный textarea.