Что может поломать макет на гридах (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). Расползается на сумму гепов, и как это пофиксить непонятно:(