Течение: поток, флекс и грид
Страница — не холст, на который кладут объекты, а течение, которое их расставляет. Научись описывать отношения вместо координат — и вёрстка переживёт содержимое, которого ты не планировал.
Все, кто пришёл из дизайн-инструмента, делают одинаковый первый ход: расставляют всё абсолютно, по координатам из макета. Выглядит идеально. Потом название продукта оказывается из четырёх слов вместо двух, и конструкция разваливается: текст налезает на текст, коробки сталкиваются, появляется полоса прокрутки там, где её быть не должно.
Ошибка не в значениях пикселей. Ошибка в категории. В дизайн-инструменте объекты кладут на холст; в браузере ты описываешь отношения, а арифметику делает браузер — с настоящим содержимым, при неизвестной ширине, шрифтом, который мог не загрузиться, на языке, который может оказаться на треть длиннее.
Вёрстку не рисуют, её описывают. Ты задаёшь отношения — арифметику считает браузер.
Нормальный поток — уже вёрстка
До всяких инструментов есть умолчание: блоки складываются вниз по странице, каждый занимает всю доступную ширину, в порядке исходника. Строчные вещи — текст, ссылки, картинки внутри текста — текут по строке, как слова, и переносятся, когда места не хватило.
Это нормальный поток, и его стоит уважать, а не заменять. Неоформленный HTML-документ уже адаптивен: он заполняет любую ширину, никогда не вылезает за края и перестраивается, когда текста стало больше. Любая техника ниже — настройка потока, а не бегство из него. Бо́льшая часть боли во фронтенде идёт от того, что с течением борются, вместо того чтобы им править.
Отсюда две привычки, которые стоит завести раньше всех остальных. Не задавай высоты. Ширина — твоё решение, высота принадлежит содержимому, а фиксированная высота — это обещание, что текст никогда не будет длиннее, чем ты вообразил. Пусть содержимое распирает. Если коробка обязана быть определённого размера, чтобы дизайн работал, в дизайне сидит допущение, которое сломают реальные данные.
Флексбокс: по одной оси
Флексбокс расставляет детей вдоль одной линии — строки или колонки — и делит между ними место.
Модель в голове — две оси. Ты выбираешь главную ось через flex-direction (строка или колонка), перпендикулярная ей называется поперечной. Дальше основную работу делают два свойства: justify-content распределяет детей вдоль главной оси, align-items расставляет их поперёк. Их путают все и довольно долго; лечится это не заучиванием, какое из них какое, а памятью о том, что одно «вдоль», другое «поперёк», и что смена направления меняет и их смысл.
Дети тоже участвуют в дележе: flex-grow — забрать долю свободного места, flex-shrink — ужаться, когда места мало, flex-basis — размер, от которого пляшем. А gap ставит расстояние между ними: пользуйся им вместо отступов у детей — старая техника всегда оставляет лишний отступ у последнего.
Флекс уместен, когда ты думаешь в одном направлении: панель инструментов, ряд кнопок, подпись рядом с полем, содержимое карточки, сложенное вертикально, всё, что должно переноситься на следующую строку, когда место кончилось.
Грид: обе оси сразу
Грид позволяет родителю сначала объявить строки и колонки, а потом расставить в них детей.
Единица, от которой всё встаёт на место, — fr, доля свободного места. Три колонки 1fr 2fr 1fr делят строку на четверти, уже за вычетом промежутков, без процентов и без арифметики с твоей стороны. repeat() экономит набор, именованные области позволяют нарисовать раскладку текстом прямо в стилях, а minmax() задаёт дорожке пол и потолок.
Комбинация, которую стоит выучить рано, потому что она делает бесплатно огромную работу: грид с колонками repeat(auto-fit, minmax(220px, 1fr)) умещает столько колонок, сколько влезет по 220 пикселей, и сокращает их количество по мере сужения окна — адаптивная сетка карточек вообще без медиазапросов и точек перелома.
Грид уместен, когда важны сразу два измерения: каркас страницы, дашборд, галерея карточек, форма с выровненными колонками подписей и полей — всё, где вещи обязаны выстраиваться и по горизонтали, и по вертикали.
Как выбирать
Вопрос и правда настолько короткий: одно измерение или два?
Флекс идёт от содержимого: размеры детей задают результат, а ты распределяешь остаток. Грид идёт от раскладки: ты объявляешь структуру, а дети в неё встают. Если ты воюешь с флексом при помощи ширин и обёрток, чтобы всё выровнялось в обоих направлениях, — тебе был нужен грид. Если объявляешь грид из одной колонки, чтобы сложить три вещи, — тебе был нужен флекс.
И они не соперники. Обычная форма настоящей страницы — грид для каркаса и флекс внутри компонентов, и вкладываются они друг в друга без проблем. Любой флекс- или грид-контейнер создаёт контекст только для своих непосредственных детей, поэтому собирать их можно на любую глубину.
Позиционирование: аварийный выход
Всё выше — поток. position — способ из него выйти, и знать стоит пять значений:
- static — умолчание, в потоке.
- relative — по-прежнему в потоке и по-прежнему занимает своё место, но визуально сдвинут. Основное применение на практике — стать якорем для чего-то абсолютного внутри.
- absolute — полностью изъят из потока и позиционируется относительно ближайшего позиционированного предка. Место он больше не занимает, поэтому под ним ничего не сдвинется.
- fixed — позиционируется относительно окна и остаётся на месте при прокрутке.
- sticky — в потоке, пока не дошёл до заданного порога прокрутки, дальше прилипает. Заголовок раздела, липнущий к верху списка.
Пользуйся этим для того, что действительно лежит поверх вёрстки: выпадашки, подсказки, значки на аватаре, оверлеи модалок, липкие шапки. Использовать их для самой вёрстки — та самая ошибка, с которой статья начиналась.
Раз уж мы здесь, смежная ловушка: z-index не сравнивает по всей странице. Элементы живут в контекстах наложения, которые создаются позиционированным элементом с z-index — а также, неожиданно, свойствами transform, opacity меньше единицы, filter и ещё парой. Ребёнок одного контекста никогда не поднимется выше соседнего контекста, каким бы огромным ни был его z-index. «Поставил z-index: 9999, и всё равно позади» — это всегда оно, и чинится поиском предка, создавшего контекст, а не добавлением ещё одной девятки.
Собери дюжину, а не прочитай про дюжину
Есть примерно дюжина раскладок, которые ты будешь собирать до конца карьеры: центрированная колонка контента, боковая панель с основной областью, липкая шапка при прокручиваемом теле, сетка карточек, панель с элементами, разведёнными по краям, форма с выровненными подписями, медиа-объект (картинка рядом с текстом), подвал, прижатый к низу короткой страницы, оверлей модалки, две колонки, схлопывающиеся в одну на мобильном, таблица с горизонтальной прокруткой, список с элементами равной высоты.
Этот список — твоя опись чанков, если пользоваться словом из сферы «Как учиться» для тех сросшихся блоков, из которых на самом деле состоит навык. Опытный разработчик, услышав «сайдбар и основная область, сайдбар схлопывается на мобильном», не выводит решение из первых принципов — он достаёт блок. А блоки строятся единственным известным способом: каждый собирается несколько раз, в разных контекстах, с проверкой результата.
Две детали отличают практику, которая работает, от практики, которая тебе льстит. Перемешивай — не собирай шесть сеток карточек подряд, чередуй типы: настоящая работа никогда не сообщает, какая техника нужна, а выбор техники и есть навык. И разноси повторы — собрать ту же раскладку заново через три дня, с нуля, не подглядывая во вчерашний код, стоит больше часа, потраченного на один раз. Усилие вспоминания — это механизм, а не признак неудачи.
На практике
Проговаривай «одна ось или две?» вслух до написания CSS. Каждый раз, месяц. Это единственное решение, ради автоматизации которого написана вся статья.
Удаляй каждую фиксированную высоту, которую написал. А потом выясняй, что действительно нуждалось в ограничении. В девяти случаях из десяти — ничего.
Тяни окно постоянно во время сборки, а не в конце. Вёрстка, которая работает только на ширине твоего окна, — не вёрстка, а скриншот.
Включи в инструментах разработчика подсветку грида и флекса. Она рисует дорожки, промежутки и линии выравнивания прямо на странице. Это самая быстрая обратная связь, какая тебе доступна, и она превращает «немного не так, и я не знаю почему» в видимый ответ.
Используй gap, а не отступы у детей. Меньше краевых случаев и никакого лишнего отступа в конце.
Пересобирай дюжину — с перемешиванием и с разносом по дням. Одна раскладка за сессию, три её варианта, следующая сессия — другой тип, и та же самая ещё раз через неделю по памяти. Шесть недель этого стоят больше любого курса.
Проверь себя
Закрой статью и ответь своими словами:
- Почему расстановка по абсолютным координатам — неверная модель страницы?
- Что такое нормальный поток и почему неоформленный документ уже адаптивен?
- В чём разница между главной и поперечной осью и какие свойства действуют по каждой?
- Сформулируй одним предложением правило выбора между флексом и гридом.
- Что означает
1frи что даётauto-fitвместе сminmax? - Когда
position: absolute— верный выбор, а когда симптом? - Почему огромный
z-indexне всегда побеждает?
Коротко
- Страницу описывают, а не рисуют: ты задаёшь отношения, а браузер считает геометрию по реальному содержимому при неизвестной ширине.
- Нормальный поток — уже работающая адаптивная вёрстка; любая техника лишь настраивает его. Не задавай высоты и позволяй содержимому распирать.
- Флекс работает вдоль одной оси:
justify-content— вдоль главной,align-items— поперёк, а дети договариваются через grow, shrink и basis. - Грид объявляет строки и колонки заранее;
fr,repeat, именованные области иauto-fitсminmaxдают адаптивные сетки без медиазапросов. - Выбирай по вопросу «одно измерение или два» и вкладывай одно в другое: грид для страницы, флекс внутри компонентов.
position— аварийный выход для того, что лежит поверх вёрстки: выпадашки, оверлеи, липкие шапки, — а не для самой вёрстки.z-indexсравнивается только внутри контекста наложения, аtransform,opacityиfilterсоздают такие контексты неожиданно.- Примерно дюжина раскладок повторяется всегда; собирай их многократно, вперемешку, а не блоками, и с разносом по дням.