что такое layout в html
Как описывать layout в коде
UI можно определить как набор вьюх, которые отображают данные, реагируют на события и при этом каким-то определенным образом расположены на экране.
Как мы размещаем элементы на экране?
What is the problem with this?
We need to use code for special cases
Описанные инструменты заточены под типовые случаи, зачастую мы не можем (или можем, но это сильно неудобно) описать расположение какого-то элемента с помощью этих инструментов. Приходится делать это в коде. Логика описания layout’а размазывается по нескольким местам.
Должен быть способ лучше.
Layout — what is it really?
Layout is a function
Что такое layout? Это описание расположения элементов. Его можно выразить в виде функции.
На вход функция получает размер контейнера и массив элементов, на выходе отдает координаты этих элементов.
Теоретически layout может быть любой – тут уж как дизайнер нарисует. Поэтому чтобы описать такую функцию нам нужен тьюринг-полный язык. UI фреймворки предлагают нам что угодно, только не тьюринг-полный язык, отсюда и проблемы. Логично было бы взять язык, на котором пишется остальная часть программы: для iOS – objc/swift, для android – java/kotlin и т.д.
What does it mean?
We can easily describe layout in code
Мы можем легко описывать layout в коде! Достаточно написать одну функцию. Часто мы будем располагать элементы относительно существующих (правее, ниже и т.д.). Можно спрятать такие вычисления в функции с читабельными именами. Под iOS это сделано в библиотеках Facade (objc), Neon (swift). Можно легко написать свою на любом языке.
Example, libraries
В iOS если вы хотите определить кастомный layout для вью, нужно переопределить метод layoutSubviews() (или viewDidLayoutSubviews() у контроллера) и задать в нем frame дочерним элементам. Этот метод вызывается при изменении размеров вью, то есть повороты, Split View и пр. зафорсят вызов этого метода.
Вот пример с официальной страницы Neon:
Неплохо для 10 строк кода, правда?
Часто UI описывается декларативно в виде разметки (xml, xaml, xib), которая потом превращается в код (Qt, Delphi, C# WinForms) или из которой создается готовый граф объектов (xib, xaml). При этом IDE предоставляют средства предпросмотра, что весьма удобно.
Описывая layout в коде мы отказываемся от инструментов предпросмотра.
Обычно layout идет сверху вниз: у нас есть размер экрана, зная этот размер мы layout’им первый слой вьюх, получив их размеры заходим глубже и т.д. Бывает нужно идти с другой стороны: растягивать контейнер по размеру содержимого. Описывать такое не всегда удобно.
Conclusion
На мой взгляд это хороший подход, если вы уже пишете UI в коде. Кода при этом получается примерно столько же как при использовании Autolayout, но работает он быстрее и предсказуемее.
Главный минус – теряется наглядность в виде инструментов предпросмотра.
Фиксированый стиль таблицы или Fixed Table Layouts
Сей опус это вольный перевод публикации Криса Койлера [Chris Coyier] от 2 Июля.
В качестве вступления
Как вы наверняка знаете, CSS свойство table-layout предназначено для управления режимом формирования ширины столбцов в таблице. Оно может принимать следующие значения: авто, фиксировано или наследственно. По умолчанию в браузерах для отображения таблицы используется стандартное поведение: браузер рассчитывает ширину строки как сумму ширины всех ячеек. Следом, по тому же принципу рассчитывает ширину 2-й строки, и если размеры какой-либо ячейки превышают размеры вышестоящей ячейки, перестраивает всю таблицу. Далее определяет ширину 3-й ячейки, 4-й и т.д. В случае если таблица имеет много, очень много рядов, расчет ширины может немного затянуться…
Однако можно (я бы даже сказал нужно) задействовать упрощенный режим который выражается в том, что браузер при определении ширины не берет в расчет содержимое последующих ячеек, а опирается на значение свойств width таблицы, столбцов и ячеек из первой строки.
В CSS (таблицы каскадных стилей) с помощью свойства table-layout мы можем управлять какой режим формирования таблицы нам нужен. Речь идет собственно об этом:
Это свойство очень хорошо поддерживается и очень даже полезно, так как позволяет вам создавать более предсказуемую разметку под вашу таблицу. По умолчанию, это свойство установлено в значение авто, и это, наверно, известно большинству из вас. Но этот режим как по мне так очень ненадежный и непредсказуемый. Приведем пример:
Режим table-layout: fixed
Если теперь задействовать режим table-layout: fixed то можно уверенно получить вполне себе предсказуемый результат по месту.
В случае применения этого режима, расчет ширины столбцов идет по первой строчке и все последующие используют это значение. Может и звучит странновато для понимания, но на самом деле все просто, приведем пример:
Применение
Крис озадачился этим вопросом, так как ему хотелось. что бы его «Pen»ы (в переводе еще смешнея звучит) в лист вью на CodePen отображались в столбиках с унифицированой шириной и не портили ему всю картину, вот что у него получилось:
Он остался очень доволен этим.
Надеюсь, все помнят, что таблицы нужны для табулированных значений и емайлов, а не для задания разметки страниц.
Посмотрите на этот практический пример (предполагаю, что почти всем понравится это):
Скорость
Что касается скорости, то говорят, что такой стиль формирования таблицы быстрейший, и это очевидно — бо остальные строки не анализируются и следовательно время генерации не зависит от длины таблицы как таковой.
Почтовики
Что касается использования в разных почтовых клиентах, то это свойство, согласно сервису мониторинга поддержки свойств CSS в почтовых клиентах, прекрасно поддерживается.
Заключение
Надеюсь, этот перевод кому-то действительно поможет лучше понять, как работает table-layout: fixed и подбросит идей по его использованию в своих проектах.
Кстати, у кого нибудь есть идеи, почему это свойство не используется по умолчанию?
7 основных понятий CSS Grid Layout с примерами, которые помогут начать работу с гридами
Авторизуйтесь
7 основных понятий CSS Grid Layout с примерами, которые помогут начать работу с гридами
CSS Grid Layout — это модуль CSS, который определяет систему макета на основе двумерной сетки, оптимизированную для дизайна пользовательского интерфейса.
В заранее определенной гибкой сетке макета мы можем размещать дочерние элементы.
Если мы посмотрим на данные с сайта Can I use то увидим, что CSS Grid Layout на данный момент поддерживается большинством современных браузеров.
Grid Layout позволяет кардинально изменять структуру визуального макета, не требуя соответствующих изменений разметки. Комбинируя медиа-запросы со свойствами CSS, можно легко добавлять адаптивность для отображения на мобильных устройствах, сохраняя при этом более идеальную семантическую структуру HTML кода.
3–4 декабря, Онлайн, Беcплатно
Давайте изучим CSS Grid Layout на примере страницы с шестью блоками.
Наш изначальный HTML код будет выглядеть так:
CSS код, он нам нужен для красоты и наглядности:
Наша страница будет выглядеть так:
Отлично, красоту мы навели, теперь давайте разберемся как же все-таки создается грид.
Свойства display: grid и display:inline-grid
А представляет он из себя одноколоночный грид с шестью рядами.
Свойства grid-gap, row-gap и column-gap
В свою очередь grid-gap объединяет два свойства:
Если использовать отдельные свойства, то код будет выглядеть следующим образом.
Свойство grid-template-columns
Теперь добавим колонки.
Ширину мы можем указывать в любых CSS единицах длины, в процентах и в единицах fr.
fr (от fraction — часть) это новая форма единицы измерения созданная для гридов, которая позволяет добавлять столько колонок, сколько мы захотим, не заботясь о конкретном значении ширины колонки. Свободное место распределяется между такими колонками пропорционально количеству fr.
1fr 1fr 1fr — такая запись означает, что мы создаем 3 колонки одинаковой ширины.
Теперь вычислим ширину каждой колонки. Первая колонка будет иметь ширину 1.5fr / 4.5fr * 400px. Что в итоге равно 133px. Соответственно рассчитаем остальные значения.
Вторая колонка 2fr / 4.5fr * 400px = 178px, третья колонка 1fr / 4.5fr * 400px = 89px. Т.е. мы могли задать колонки таким образом:
grid-template-columns: 133px 178px 89px;
В сумме общая ширина равна 400px.
В этом случае, если мы захотим добавить еще одну колонку либо наоборот убрать, нам придется пересчитать и заменить все значения вручную. С единицей измерения fr все пересчитается автоматически.
Свойство grid-template-rows
Это свойство позволяет добавлять строки в грид.
Давайте отвлечемся и посмотрим подробнее, что из себя представляет наш грид и разберем несколько новых понятий.
Грид-контейнер — элемент, в котором находится сетка грида.
Грид-линии — невидимые вертикальные и горизонтальные линии, разделяющие грид на ячейки. У грид линий есть нумерация, а также им можно задавать имена. На изображении помечены красными (вертикальные линии) и фиолетовыми стрелками (горизонтальные линии).
Грид-полосы — пространство, которое ограничено парой соседних грид-линий. Бывают вертикальные и горизонтальные.
Грид-ячейки — то, что получается на пересечении двух грид-полос. По аналогии с ячейками таблицы. На картинке это синие блоки с буквами, в количестве шести штук.
Грид-области — прямоугольники из смежных грид-ячеек. Каждая грид-область ограничена двумя парами грид-линий (парой вертикальных и парой горизонтальных).
Грид-интервалы — пустые пространства между соседними грид-полосами.
Свойства grid-auto-rows, grid-auto-columns и grid-auto-flow
В примере создания колонок вы могли заметить, что мы не задавали колонки. Это произошло потому что колонки и строки могут быть заданы в явном и в неявном виде.
В свою очередь для колонок есть свойство grid-auto-columns которое выставляет ширину колонки.
Хочу заметить важную вещь, это то что ячейки выстраиваются вдоль колонок, а не строк. A и B в первой колонке, C и D во второй и т.д.
Как видим, третья колонка добавилась автоматически и ее ширина формируется на основе контента в самой широкой ячейке. Давайте укажем ширину для этой колонки.
Получаем уже привычный нашему глазу грид.
Функция repeat()
Эта функция позволяет сокращать объявление колонок и строк в случае повторяющихся значений.
Например у нас есть такой код.
Используя функцию repeat мы можем переписать наш код следующим образом.
Функция minmax(), auto-fill и auto-fit
Рассмотрим пример с использованием этой функции.
Запись minmax(120px, 1fr) означает, что минимальный размер столбца равен 120px, а максимальный — 1fr.
auto-fill делает наш грид адаптивным, то есть в нем элементы меняют свои позиции с тем, как мы меняем размер окна браузера.
Ширина окна браузера 450px:
Ширина окна браузера 300px:
Ширина окна браузера 200px:
Чтобы показать разницу, рассмотрим следующие примеры:
С auto-fill у нас три колонки
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
С auto-fit пустая колонка исчезает
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
Свойства grid-template-areas, grid-area
Для начала отредактируем наш html-код и используем в нем семантические теги.
C помощью grid-area зададим псевдонимы для наших блоков.
С помощью grid-template-areas определим шаблон страницы.
Это делается легко и быстро. Давайте попробуем.
Вот и все, мы изменили шаблон, потратив минимум времени!
В качестве заключения хотелось бы сказать, что в данном руководстве не ставилась цель охватить все аспекты CSS Grid Layout, эта тема довольно обширная. Мы рассмотрели основы теории и примеры, которые помогут начать работу с гридами.
Основные понятия Grid Layout
CSS Grid Layout представляет двумерную сетку для CSS. Grid (здесь и далее подразумевается CSS Grid Layout ) можно использовать для размещения основных областей страницы или небольших элементов пользовательского интерфейса. В этой статье описывается компоновка сетки CSS и новая терминология, которая является частью спецификации CSS Grid Layout Level 1. Функции, показанные в этом обзоре, будут более подробно описаны в остальной части данного руководства.
Что такое Grid?
Фиксированные и гибкие размеры полос
Расположение элемента
Вы можете размещать элементы в заданном месте Grid, используя номера строк, имена или путём привязки к области Grid. Grid также содержит алгоритм управления размещением элементов, не имеющих явной позиции в Grid.
Создание дополнительных полос для хранения контента
Вы можете определить явную сетку с макетом сетки, но спецификация также касается контента, добавленного за пределами объявленной сетки, при необходимости добавляя дополнительные строки и столбцы. Включены такие функции, как добавление «столько столбцов, сколько будет помещено в контейнер».
Управление выравниванием
Grid содержит функции выравнивания, чтобы мы могли контролировать, как элементы выравниваются после размещения в области сетки, и как выравнивается весь Grid.
Управление перекрывающимся контентом
Grid контейнер
Мы создаём grid контейнер, объявляя display: grid или display: inline-grid на элементе. Как только мы это сделаем, все прямые дети этого элемента станут элементами сетки.
В этом примере есть div, содержащий класс wrapper с пятью потомками
Сделаем wrapper grid контейнером
Все прямые потомки теперь являются grid элементами. В веб-браузере вы не увидите никакой разницы в отображении элементов, поскольку Grid создал единую сетку столбцов для элементов. На данный момент вам может оказаться удобным работать в Firefox Developer Edition, в котором есть Grid Inspector, доступный как часть инструментов разработчика. Если вы просмотрите этот пример в Firefox и проверите Grid, вы увидите маленький значок рядом с display: grid. Нажмите этот значок, и сетка на этом элементе будет наложена в окне браузера.
По мере вашего обучения и последующей работы с CSS Grid Layout этот инструмент даст вам лучшее визуальное представление о том, что происходит с вашей сеткой.
Если мы хотим, чтобы это стало более похожим на сетку, нам нужно добавить полосы.
Grid Tracks
Можно дополнить пример выше, добавив свойство grid-template-columns и задав размеры полос колонок.
В примере ниже мы создаём сетку с шириной колонки 200px. Каждый дочерний элемент сетки будет располагаться в отдельной ячейке сетки.
Единица измерения fr
Размер треков (полос) может быть задан с помощью любой единицы длины. Спецификация также вводит дополнительную единицу длины, позволяющую создавать эластичные (flexible) грид-треки. Новая единица длины fr представляет собой долю (fraction) доступного пространства в грид-контейнере. Определение грида в коде ниже создаст три трека равной длины, которые будут увеличиваться и уменьшаться в размерах в соответствии с доступным пространством.
В последнем примере смешаем треки с абсолютной длиной и треки с длиной во fr. Размер первого трека 500 пикселей, и эта фиксированная ширина убирается из доступного пространства. Оставшееся пространство разбивается на три части и пропорционально разделяется между двумя эластичными треками.
Задание треков с помощью нотации repeat()
можно записать вот так:
Repeat-нотацию можно использовать как часть списка треков. В следующем примере создаётся грид с начальным треком шириной в 20 пикселей, шестью треками шириной в 1fr и последним треком шириной в 20 пикселей.
Явный и неявный грид
Масштабирование треков и minmax()
При задании размеров явного грида или при определении размеров автоматически создаваемых колонок или строк может возникнуть следующая ситуация: мы хотим определить для треков минимальный размер, но при этом быть уверенными, что при необходимости треки растянутся, чтобы вместить весь добавленный в них контент. Например, нам нужно, чтобы строки никогда не становились меньше 100 пикселей, но если контент занимает, скажем, 300 пикселей в высоту, мы хотим, чтобы строка тоже стала 300 пикселей.
Grid-линии
Размещение элементов по линиям
В дальнейшем мы детально рассмотрим размещение элементов с помощью линий, а пока остановимся на простом способе. При размещении элемента мы ссылаемся именно на линию, а не на трек.
Не забывайте, что вы можете использовать Grid Inspector в Firefox Developer Tools, чтобы посмотреть, как элементы размещаются по линиям грида.
Grid-ячейки
Grid-области
Зазоры (Gutters)
Вложенные гриды
Грид-элемент может быть и грид-контейнером. В следующем примере у нас есть созданный ранее трёхколоночный грид, в котором размещены два элемента. В данном случае у первого элемента есть несколько подэлементов. Поскольку эти подэлементы не являются прямыми потомками грида, они не участвуют в структуре грида и отображаются в соответствии с нормальным потоком документа.
В данном случае вложенный грид не связан с родительским. Как вы можете видеть, он не наследует значение свойства grid-gap (en-US) родительского элемента и линии вложенного грида не выравниваются по линиям родительского грида.
Подгрид (Subgrid)
Примечание. Эта функция поставляется в браузере Firefox 71, который в настоящее время является единственным браузером для реализации subgrid.
Размещение элементов с помощью z-index
Несколько грид-элементов могут занимать одну и ту же ячейку. Если вернуться к нашему примеру с элементами, размещёнными по номерам линий, мы можем сделать так, чтобы два элемента перекрывались:
Управление порядком отображения
Что дальше?
В данной статье мы очень кратко рассмотрели спецификацию Grid Layout. Поиграйте с примерами кода и переходите к следующей части нашего гида, где мы детально покопаемся в недрах CSS Grid Layout.
Адаптивный layout — как переверстать весь проект, не перевёрстывая его
Вёрстка — это первое, чему обычно учатся начинающие фронтендеры. Но когда вёрстки в вашем проекте становится очень много, поменять что-то — особенно везде и сразу — становится непросто. Переверстать большой проект, как правило, почти нереально. И если начинает казаться, что в какой-то задаче это нужно, вероятно, пришло время для более нестандартных методов.
Всем привет! Меня зовут Юрий Голубев, я разрабатываю frontend в Почте Mail.ru. Сегодня я хочу поделиться опытом того, как мы добавили адаптивности и возможности кастомизации в интерфейс, а заодно — открыли новый для себя способ написания адаптивных компонентов.
Почта прошла долгий путь от списка текстовых писем до сложной системы с динамическим контентом, поиском, файлами, папками и так далее. С ростом функционала выросла сложность интерфейсов и когнитивная нагрузка. Теперь пользователь может создать дерево вложенных папок, а на экране чтения может быть свёрстанное HTML-письмо с вложениями. А ещё некоторым пользователям удобнее работать со списком папок, списком писем и просмотром письма на одном экране.
Сделать всё сразу невозможно, поэтому современные почтовые интерфейсы можно настраивать. Но настроек вида бывает недостаточно: кто-то готов пожертвовать названиями папок ради широкого просмотра письма, кто-то наоборот. Похожие запросы часто всплывали на юзабилити исследованиях, опросах и в обращениях в поддержку.
Подобные запросы явно не получится решить чекбоксом или двумя: тут слишком много вариантов. Хочется чего-то более точного, и желательно — прямо в интерфейсе, чтобы пользователь мог изменять почту под свои потребности «на лету».
Таким образом, мы пришли к задаче — дать пользователю возможность персонализировать интерфейс с помощью изменения размера колонок (они же области с контентом). Персонализировать — с точностью до пикселей.
Ну и что тут сложного?
Изменить размер колонки — не основная проблема. Что важно: чтобы контент адаптировался не под размер экрана пользователя, а под размер колонки.
Почта Mail.ru — технически сложный проект. И верстка в нем непростая. На уровне layout’a мы поддерживаем компактный вид, режим отображения списка и чтения письма на одном экране, а ещё есть адаптация под b2b-пользователей.
При этом layout должен адаптироваться на экранах от 768px до бесконечности. В зависимости от разных параметров изменение ширины viewport’а по-разному перестраивает layout приложения. А теперь мы пришли к тому, что хотим настраивать ширину колонок.
Так что тут нужно было обойтись без того, чтобы переверстывать всё. Потому что это заняло бы и правда очень много времени.
Планы.
Уже на этапе проектирования задачи стало ясно, что придется затронуть довольно большое количество кода. Настолько большое, что вместить это всё в один релиз мы бы просто не рискнули. Было решено делать и релизить задачу поэтапно:
… и суровая реальность
Планы — это хорошо, но уже на первом этапе мы столкнулись с проблемой. Интенсивно всё протестировав, прогнав автотесты и загоняв бедных тестировщиков до седьмого пота проверкой во всех распространенных браузерах, мы перекрестились и выкатили релиз. Однако через какое-то время после релиза мы стали наблюдать небольшие пики в графиках сломанной верстки.
Пики укладывались в рамки погрешности. К сожалению, точных инструментов для детекта подобных проблем у пользователей пока не придумано. ☹️
Но со временем стало понятно, что пики становятся выше и не собираются исчезать. Количество попаданий в метрику было небольшим, так что мы знали, что проблема не массовая.
Дело стояло за одним — разобраться в том, что же случилось, и что есть общего у этой небольшой группы пользователей. В чем же было дело? Для этого придется обратиться к деталям реализации.
Изначально колонки в нашем интерфейсе были спозиционированы через position: absolute и отступы (которые регулировались в JS по подписке на matchMedia). Прежде чем вы начнете закидывать меня камнями, уточню: когда это делалось, мы ещё рассчитывали на поддержку Internet Explorer 9. Так что наши опции были, скажем мягко, ограничены.
Однако с тех пор прошло много времени, поддержка IE9 (и даже IE10) канула в лету, и у нас поддерживаются только новые браузеры, пользователи старых же отправляются в «легкую» версию почты. Также мы успели обкатать Grid Layout (в том числе в IE11) в паре более мелких проектов. Он выглядел замечательным инструментом для текущей задачи, позволяя задавать и удобно изменять ширину колонок в одном месте.
Уход от position: absolute также имеет дополнительные преимущества — элементы (колонки) теперь располагаются на одном слое. Это, как правило, ускоряет отрисовку, а также улучшает плавность анимаций. Это важно для подобной задачи, в которой размер элементов должен меняться в ответ на действия пользователя.
И мы решили воспользоваться им. Что ж, обратно к ошибкам. Довольно скоро стало понятно что общее нечто у пользователей есть — браузеры. Вот как выглядели юзер-агенты:
Казалось бы, всё тривиально, мы каким-то чудом проглядели слишком старую версию Firefox. Но нет, поддержка Grid Layout в FF52 определенно есть. Однако поиск по другим подстрокам в юзер-агенте дал результат: оказывается, в довольно старые времена, некие энтузиасты сделали форк движка Firefox, который получил название Goanna. На нем на данный момент существует несколько браузеров. Ну а 3-я версия движка, Goanna/3.x, притворяется FF52, хотя не поддерживает часть функций которые есть в FF52, в том числе — вы угадали — Grid Layout.
Порадовавшись неожиданной прозорливости метрик, мы откатили этот релиз и внесли исправления.
Теперь вместо Grid Layout используется flex + css-variables (для того чтобы удобно было изменять ширину колонок из кода).
Адаптация контента к ширине
Что ж, перейдём к второму этапу. Любой веб-разработчик в какой-то мере знает, как адаптировать интерфейс под размер экрана. Для этого придумано много инструментов, как напрямую в браузерах, так и сделанных разработчиками в виде библиотек.
Чтобы освежить память, давайте пройдемся по самым типовым вариантам реализации.
«Резиновый» интерфейс:
Меняется размер экрана — меняется размер элементов. Пропорции и структура остаются неизменными.
Как реализуем: определяем ширину элементов в процентах от контейнера/экрана.
«Адаптивный» интерфейс:
Меняется размер экрана — меняется структура элементов и иногда размеры элементов. Например, колонка навигации может превратиться в бургер-меню на мобильной версии. Как правило, у современных сайтов используется адаптивность хотя бы для смены десктопного вида на мобильный.
Очень сложно сделать так, чтобы интерфейс выглядел хорошо и на крупном ПК и на маленьком телефоне, не изменяя структуры страницы.
Как реализуем: на данный момент есть несколько вариантов, основные перечислены далее.
Что же тогда тут сложного, если уже всё придумано?
Разговор выше идет о адаптации под размер окна браузера. А в данном случае нам надо адаптироваться под размер колонки интерфейса.
А есть ли разница?
Да… И нет. Чуть выше (под спойлером) можно найти описание того, что используется для адаптации интерфейса под размер экрана. А что касательно размера одной части интерфейса? Например, в нашем интерфейсе есть компонент шапки, который состоит из фона, логотипа, поиска и тулбара, который занимает всё доступное место. При изменении ширины экрана набор кнопок внутри тулбара может изменяться. Кнопка с иконкой может сворачиваться до иконки или скрываться в трех точках.
Что мы можем использовать тут из того, что уже упоминалось?
И что, скажете вы, всё? Но как же нам поступить с кнопками? Тут явно не хватит простой ширины в процентах или flexbox-a. Мы ограничены таким небольшим списком вещей, и никак не можем сказать браузеру конкретнее, что мы хотим? Что ж, мы и правда не можем использовать для этого media queries (хотя ситуация может вскоре измениться). Но API, позволяющее достичь похожего поведения, существует (хоть и придется потрудиться руками).
ResizeObserver — API, которое позволяет следить за изменением размера конкретного элемента и реагировать на него. Как раз то, что нам нужно!
Однако работать с подобным API напрямую не очень удобно. Более того, оно делает куда больше, чем нам действительно нужно (об этом чуть дальше).
Что же нам нужно?
Нам нужен способ сделать компонент, который будет реагировать на изменение размера его главного элемента (контейнера) и изменять свой внешний вид соответственно. Чтобы не отвлекаться на детали бизнес-логики, рассмотрим данный подход на примере попроще, чем тулбар: на адаптивной кнопке.
Что мы делаем?
Конечно же пишем обертку! И позволяем себе некоторые упрощения в процессе. У нас есть некоторые дополнительные условия:
В итоге, после некоторых экспериментов, я пришел к вот такому интерфейсу:
Так выглядит обработчик для кнопки с иконкой, у которой может быть или не быть текста, в зависимости от ширины кнопки. Всё предельно просто:
Всё, наш компонент умеет адаптироваться к любому размеру! Ну, конечно, только настолько, насколько мы в него это заложили.
Конечно же, в таком способе создавать адаптивные компоненты есть свои недостатки. Одним из них является возможность зацикливания в ResizeObserver. Как такое может случиться? Очень просто. ResizeObserver вызывает коллбэк по изменению размера наблюдаемого элемента. Таким образом, если ширина изменится в результате выполнения коллбэка, то из-за этого коллбэк вызовется снова.
А если ширина продолжит меняться после каждого вызова коллбэка, то коллбэк будет вызываться бесконечно. Из этого следует первая вещь в списке «как делать не надо».
А именно — не меняйте размеры элемента за которым вы наблюдаете.
Но, вы скажете, это же очевидно! Да, возможно. Что ж, это не единственный способ сесть в лужу. 🙂 Даже если вы не измените размер руками, достаточно сделать что-то, что приведет к изменению размера — и это выльется в тот же самый результат.
Здесь стоит упомянуть важный момент. Размер, за которым следит ResizeObserver, и который возвращается вам в виде contentRect — это, как следует из названия, размер, доступный для контента. То есть за вычетом padding и border. Так что вторая вещь, которую делать не стоит — это менять padding или border у элемента за которым вы наблюдаете.
CSS Containment задумывался именно с целью решить подобные проблемы. Но, пока все браузеры не получат их поддержку (даже если появится полноценный полифилл), всё ещё можно будет выстрелить себе в ногу, просто заметить это будет сложнее.
Ограничение свободы (кастомизации)
Не стоит забывать, что помимо того чтобы дать возможность изменять пропорции интерфейса, необходимо так же дать и рамки, в которых это можно делать. Вряд ли вы действительно хотите, чтобы можно было сжать сайдбар до одного пикселя.
В нашем случае это свелось к довольно тривиальной системе (которую, однако, было труднее продумать, чем ожидалось):
Когда мы совмещаем правильную ширину колонок и компоненты, которые могут к ней адаптироваться, паззл складывается: мы получаем адаптивный интерфейс, в котором пользователь может изменять размеры элементов, и всё работает!
И что, всё?
Да, всё. Получилось немного, правда? Однако это было довольно крупной задачей с нашей стороны. Но большая часть произошла «за кадром», в переписывании и рефакторинге кода, который просто не мог «подружиться» с новой системой.
Советовал бы ли я такой подход для нового стартующего проекта? Та ли это «убер-фича», которую вы можете прикрутить у себя завтра? Пожалуй, нет. Всё же пока это требует довольно больших усилий для реализации. Без готовых более или менее универсальных инструментов это может быть сложной задачей.
Однако замечу, что адаптивные компоненты вы можете начать создавать уже сегодня.
Использование CSS Containment может помочь нивелировать возможные минусы в перформансе от реализации адаптивности в JS, а через какое-то время, возможно, мы придем и к полноценным Container Queries, на которые будет легко мигрировать с текущего подхода.
Возможно, стоит вспомнить обо всём этом, когда вы будете писать ваш следующий универсальный компонент? 🙂
И, на прощание, ещё немного цифр!
В процессе реализации задачи мы: