Что может поломать макет на гридах (CSS Grid Layout)
Перевод статьи BREAKING THE GRID с сайта daverupert.com для CSS-live.ru, автор — Дейв Руперт
Два способа поломать CSS-гриды, и как их починить
Как фронтенд-разработчика, меня ничто так не раздражает, как неожиданное появление горизонтального скроллбара на сайте. Однако, выстраивая раскладку страницы заказа с помощью CSS-гридов, я на удивление обнаружил что-то мистическое, что поломало мой контейнер. Я полагал, что с размерами у гридов всё решается автоматически.
В итоге я нашёл два способа поломать CSS-гриды. Как это бывает, я умудрился в одной раскладке сделать и то, и другое.
№1: Элементы с overflow-x поломают грид
Для нашего магазина использовался паттерн подменю, которое могло бы прокручиваться с помощью overflow-x, чтобы показать больше вариантов оплаты. Даже с примененным overflow-x грид-элемент растягивался до scrollWidth подменю. Оказывается, любой элемент с overflow-x, будь то блок <code> или адаптивная таблица, поломали бы грид. Вот наглядный пример.
See the Pen Overflow-x Elements Inside CSS Grid (bug? behavior?) by Максим (@psywalker) on CodePen.
Обычно с решением проблемы горизонтального переполнения справляется overflow-x: auto. Но не в этот раз. Ещё идея — обернуть элемент, попробовав всякие трюки с overflow: hidden, чтобы заставить раскладку подчиняться. Тоже нет.
Дело в том, что по умолчанию у грид-элементов стоит min-width: auto и их размер автоматически устанавливается по контенту в них. Оно устанавливает их min-width по ширине переполняющего блока. Поэтому для исправления глубоко вложенного переполняющего дочернего элемента вы, вопреки интуиции, не обращаете внимания на родительский элемент, а идете вверх по DOM-дереву до самого грид-элемента и обнуляете ему min-width.
.grid > * { min-width: 0; }
Благодаря этому размеры грид-элемента с переполнением контента установятся правильно. Но не всегда.
№2: Контролы формы поломают грид
В нашей раскладке тоже использовался грид для размещения полей формы бок о бок. Я создал тестовый пример на CodePen с различными типами полей, чтобы изолировать эту проблему. При уменьшении окна браузера до момента появления горизонтального скролла (~380px) можно видеть, как поля выходят за границы грид-полосы.
See the Pen CSS Grid PRBLMZ by Максим (@psywalker) on CodePen.
В итоге, чтобы пофиксить подобные поля, придётся применить max-width: 100%, и хотя это в основном исправляет такое поведение в Chrome и Safari, некоторые элементы в этих браузерах и все подобные элементы в Firefox и Edge по-прежнему вываливаются за грид-полосу.
Потому что элементы <input> и им подобные (<img>, <progress>, <select>, <video>) обладают потенциальной способностью быть чем-то, называемым «Замещаемыми элементами»
Что ещё, чёрт возьми, за «Замещаемые элементы»?
Согласно MDN, замещаемый элемент — элемент, представление которого выходит за рамки CSS. Это ситуация, когда браузер получает разметку и внедряет элемент с чем-то вроде Shadow DOM. <video> — подходящий пример, но большинство контролов формы также попадают под это описание.
Замещаемые элементы существуют на какой-то спецификационной ничейной территории. Правила их оформления и отображения четко не определены, только лишь то, что у них «часто есть внутренние размеры». Поэтому каждый браузер по своему трактует внешний вид по умолчанию определённых элементов и использование замещаемых элементов в целом. На скриншоте ниже видно, как в зависимости от браузера выглядят замещаемые элементы и их размеры.

В нашей ситуации у некоторых элементов input есть фантомные «внутренние размеры», применяемые браузером. Они не отображаются в качестве браузерного стиля, ни во вкладке «Стили», ни в «Вычисленные стили», но их min-width около ~180px для стандартного <input type="text">.
Чтобы кроссбраузерно пофиксить «вываливание» элементов за границы грид-полосы, придётся переопределить этот внутренний размер замещаемых элементов.
/* Применяем max-width для замещаемых элементов и контролов формы. */
img, video, audio, canvas, input,
select, button, progress { max-width: 100%; }
/* Заставляем поля с типом file и submit переносить текст */
input[type="file"],
input[type="submit"] { white-space: pre-wrap; }
/* Чиним прогрессбар и поле-ползунок */
progress,
input[type="range"] { width: 100%; }
/* Фиксим поля с типом Number в Firefox */
@supports (--moz-appearance: none) {
input[type="number"] { width: 100%; }
}
Здесь есть побочный эффект, когда прогрессбар, и поля range и number теперь всегда занимают 100% грид-полосы. Это можно переопределить и настроить.
Можно ли назвать это багами гридов? Не совсем.
Такое поведение, как указал некто на StackOverflow, соответствует спецификации. Гриды перенимают поведение флексбоксов относительно min-width: auto, а у замещаемых элементов их min-width зависит от внутренних размеров. Так что само по себе это не баг.
Тем не менее, почти для каждой из моих раскладок мне приходилось применять вспомогательный класс к капризным грид-элементам. Чтобы у него было запоминающееся фирменное название, я назвал его «Fit Grid» (по-русски можно передать как «Влит-в-грид» — прим. перев.).
/*
_______ ___ _______ _______ ______ ___ ______
| || | | | | || _ | | | | |
| ___|| | |_ _| | ___|| | || | | | _ |
| |___ | | | | | | __ | |_||_ | | | | | |
| ___|| | | | | || || __ || | | |_| |
| | | | | | | |_| || | | || | | |
|___| |___| |___| |_______||___| |_||___| |______|
Дейв Руперт
Читайте ещё: https://daverupert.com/2017/09/breaking-the-grid/
*/
/*
* Удаляем `min-width: auto` из элементов грида.
* Чиним элементы с overflow-x.
*/
.fit-grid > * { min-width: 0; }
/* Применяем max-width к замещаемым элементам и контролам формы */
.fit-grid img,
.fit-grid video,
.fit-grid audio,
.fit-grid canvas,
.fit-grid input,
.fit-grid select,
.fit-grid button,
.fit-grid progress { max-width: 100%; }
/* Заставляем поля с типом file и submit переносить текст */
.fit-grid input[type="file"],
.fit-grid input[type="submit"] { white-space: pre-wrap; }
/* Чиним прогрессбар и поле-ползунок */
.fit-grid progress,
.fit-grid input[type="range"] { width: 100%; }
/* Фиксим поля с типом Number в Firefox */
@supports (--moz-appearance: none) {
.fit-grid input[type="number"] { width: 100%; }
}
Гриды для тяжелых раскладок не должны задумываться, что в них за контент, и должны выдерживать самые разные сценарии. Было бы неплохо внедрить это в каждый проект, чтобы сделать грид-элементы максимально устойчивыми. Пусть это и не баг, но я отношу это к территории «Clearfix 2.0».
Как по мне, так замещаемые элементы всегда должны подчиняться и вмещаться в грид-полосы. Я был бы рад, если бы браузеры сошлись на этом. Единственный вопрос, который крутится в моей голове, это должны ли контролы формы, такие как прогрессбар, ползунок и числовые занимать всю грид-полосу. Решение — input, select { width: 100% }, но нутром чую, что не все этого захотят.
Если у вас есть улучшения, дайте мне знать, и я опубликую их на GitHub, но, пожалуйста, перед требованием исправить что-то, всё же проверяйте это в каждом браузере. Спасибо, d00dz.
Спасибо Грегу Уитворту из Microsoft Edge за помощь в отладке и лучшем понимании замещаемых элементов.
P.S. Это тоже может быть интересно:
Clearfix умер, да здравствует clearfix!
Во втором примере фраза «this is a really wide select box» скорее несёт смысл «действительно широкий select box», правда я не знаю, переводится ли сам «select box» по-нормальному.
Такое впечатление, что Web не инженеры разрабатывали, а гуманитарии. Или математики. Что, впрочем, одно и то же… Сколько же еще в нем скрытых багов, пока не выявленных, разгуливает?
Ещё макет может поломать column-gap, он почему-то не учитывается даже при box-sizing: border-box если ширина контейнера установлена в относительных величинах (% или vw). Расползается на сумму гепов, и как это пофиксить непонятно:(